Use requestAnimationFrame to achieve js animation with good performance. First, let me give you a brief introduction to what are the advantages of requestAnimationFrame compared to setTimeout and setInterval?
Example 1:
requestAnimationFrame has two main advantages over setTimeout and setInterval:
1. requestAnimationFrame will concentrate all DOM operations in each frame and complete them in one redraw or reflow, and the time interval of redraw or reflow closely follows the refresh frequency of the browser. Generally speaking, This frequency is 60 frames per second.
2. In hidden or invisible elements, requestAnimationFrame will not be redrawn or reflowed, which of course means less cpu, gpu and memory usage.
Like setTimeout and setInterval, requestAnimationFrame is a global function. After calling requestAnimationFrame, it will ask the browser to redraw according to its own frequency. It receives a callback function as a parameter. When the browser is about to redraw, this function will be called and the callback will be passed to this function. The function takes the time as a parameter. Since the function of requestAnimationFrame is only one-time, if you want to achieve the animation effect, you must call requestAnimationFrame continuously, just like we do using setTimeout to implement animation. The requestAnimationFrame function returns a resource identifier, which can be passed as a parameter to the cancelAnimationFrame function to cancel the requestAnimationFrame callback. How about it? Is it very similar to clearTimeout of setTimeout?
Therefore, it can be said that requestAnimationFrame is a performance-optimized version of setTimeout specifically tailored for animation. The difference is that requestAnimationFrame does not specify the running time of the callback function itself, but executes the callback according to the browser's built-in refresh frequency. This Of course, you can achieve the best effect of animation that the browser can achieve.
At present, some of the browsers that support requestAnimationFrame still have their own private implementations, so the prefix must be added. For browsers that do not support requestAnimationFrame, we can only use setTimeout, because the usage of the two is almost the same, so the two are compatible. It's not difficult. For browsers that support requestAnimationFrame, we use requestAnimationFrame, and for browsers that do not support it, we gracefully downgrade to the traditional setTimeout. By encapsulating them, you can get an API that is uniformly compatible with all major browsers.
The code can be viewed here: https://gist.github.com/chaping/88813f56e75b0fd43f8c
var lastTime = 0; var prefixes = 'webkit moz ms o'.split(' '); //各浏览器前缀 var requestAnimationFrame = window.requestAnimationFrame; var cancelAnimationFrame = window.cancelAnimationFrame; var prefix; //通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式 for( var i = 0; i < prefixes.length; i++ ) { if ( requestAnimationFrame && cancelAnimationFrame ) { break; } prefix = prefixes[i]; requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ]; cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ]; } //如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout if ( !requestAnimationFrame || !cancelAnimationFrame ) { requestAnimationFrame = function( callback, element ) { var currTime = new Date().getTime(); //为了使setTimteout的尽可能的接近每秒60帧的效果 var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); var id = window.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall ); lastTime = currTime + timeToCall; return id; }; cancelAnimationFrame = function( id ) { window.clearTimeout( id ); }; } //得到兼容各浏览器的API window.requestAnimationFrame = requestAnimationFrame; window.cancelAnimationFrame = cancelAnimationFrame;
This way we can use requestAnimationFrame and cancelAnimationFrame on all browsers.
Here is a simple example to illustrate how to use requestAnimationFrame for animation. The following code will move the div with the ID demo to the right to 300px
<div id="demo" style="position:absolute; width:100px; height:100px; background:#ccc; left:0; top:0;"></div> <script> var demo = document.getElementById('demo'); function rander(){ demo.style.left = parseInt(demo.style.left) + 1 + 'px'; //每一帧向右移动1px } requestAnimationFrame(function(){ rander(); //当超过300px后才停止 if(parseInt(demo.style.left)<=300) requestAnimationFrame(arguments.callee); }); </script>
Example 2:
JavaScript animations have always been implemented through timers and intervals. Although the use of CSS transitions and animations has made animation more convenient in web development, the implementation of animation based on JavaScript has changed little over the years. It wasn't until the release of Firefox 4 that the first improvements to JavaScript animations were introduced. But to fully understand the improvement, it will help us understand how web animation evolves and improves.
Timer
The first pattern for creating animations is to use chained setTimeout() calls. For a long time in the days of Netscape 3′s hayday, developers remembered a kind of fixed latest quotation status bar that could be seen everywhere on the Internet. It usually looked like this:
(function(){ var msg = "新的广告", len = 25, pos = 0, padding = msg.replace(/./g, " ").substr(0,len), finalMsg = padding + msg; function updateText(){ var curMsg = finalMsg.substr(pos++, len); window.status = curMsg; if (pos == finalMsg.length){ pos = 0; } setTimeout(updateText, 100); } setTimeout(updateText, 100); })();
If you want to test this code in the browser, you can create a new one
Tags are used to simulate window.status, for example: newsticker example
This annoying web pattern was later met with resistance to the disabling of window.status, but with the release of Explorer 4 and Netscape 4, browsers gave developers more control over page elements for the first time. Technology appears again. In this way, a new animation mode appears that uses JavaScript to dynamically change the size, position, color, etc. of elements. For example, here is an animation that changes the width of a div to 100% (similar to a progress bar):
(function(){ function updateProgress(){ var div = document.getElementByIdx_x("status"); div.style.width = (parseInt(div.style.width, 10) + 5) + "%"; if (div.style.width != "100%"){ setTimeout(updateProgress, 100); } } setTimeout(updateProgress, 100); })();
尽管动画在页面上的地方不同,但基本原理却是一样的:做出改变,用setTimeout()间隔使页面更新,然后setTimeout又执行下一次变化,这个过程反复执行,直到动画完成(见进度条动画),早期的状态栏动画是相同的技术,只是动画不一样而已。
间隔动画Intervals
随着成功将动画引入web,新的探索开始了。一个动画已经无法满足了,现在需要多个动画。首次尝试为每个动画创建多个动画循环,在早期的浏览器中使用setTimeout()来创建多个动画是有点复杂的,所以开发商开始使用setInterval()一创建单一的动画循环,来管理页面上所有的动画,一个使用wetInterval()的基本动画像这样:
(function(){ function updateAnimations(){ updateText(); updateProgress(); } setInterval(updateAnimations, 100); })();
创建一个小动画库,updateAnimations()方法将每一个动画(同时看到一个新闻股票和进度条在一起运行)循环执行并进行适当的改变。如果没有动画需要更新,该方法可以退出而不做任何事情,甚至停止动画循环,直到有更多的动画更新做好准备。
动画问题比较棘手的问题是延迟应该为多少。间隔一方面必须足够短,从而使不同的动画都能流畅的进行,别一方面还要足够长,使得浏览器可以完成渲染。大多数浏览器的刷新频率为60HZ,即每秒60次刷新,大多数浏览器的刷新频率都不会比这个更频繁,因为他们知道,最终用户是得不到更好的体验的。
鉴于此,为流畅动画的最佳时间间隔为1000毫秒/ 60,约17ms。在这个频率你会看到流畅的动画,那是因为你最大的接近了浏览器能达到的频率。跟以前的动画相比,你会发现17ms间隔的动画更加平滑,也更快(因为动画更新更频繁,没有做其他任何修改的情况下),多个动画可能需要节流,以免17ms的动画完成得太快。
问题
即使使用setInterval()为基础的动画循环比多套使用setTimeout()的动画循环高效,这里还是存在问题。无论是setInterval()还是setTimeout()都无法达到精确,这个延迟即你指定的第二个参数仅仅表示何时代码会添加到浏览器的可能被执行的UI线程队列中。如果队列中有其他工作在此之前,那代码将会等到他完成才会执行。简而言之,毫秒级的延迟不是表示何时代码会执行,而是表示何时代码会添加进队列。如果UI线程处于繁忙状态或在处理用户动作,那么代码将不会被马上执行。
平滑动画的关键是理解下一帧何时被执行,直到现在都没有一个方法来保证下一帧将会在浏览器中被绘制。随着的日益流行和新的基于浏览器的游戏的出现,开发商对setInterval()和setTimeout()的不精准越来越感到失望。
浏览器的计时器分辨率加剧了这个问题,计时器对毫秒不精准,这里有一些常见的计时器分辨率:
Internet Explorer 8 and earlier 15.625ms
Internet Explorer 9 and later 4ms.
Firefox and Safari ~10ms.
Chrome has a timer 4ms.
IE在版本9之前的的分辨率为15.625,所以0~15之间的任意值可能是0或15,但没有分别。IE9的计时器分辨率改进为4ms,但涉及到动画时也是不具体的,chrome的计时器分辨率为4ms,firefox 和 safari的为10ms。因此即使你把间隔设定为最佳的显示效果,你也仅仅是得到这个近似值。
mozRequestAnimationFrame
Mozilla 的 Robert O'Callahan 在思考这个问题,并想出了一个独特的方案。他指出CSS transitions 和 animations的优势在于浏览器知道哪些动画将会发生,所以得到正确的间隔来刷新UI。而javascript动画,浏览器不知道动画正在发生。他的解决方案是创建一个mozRequestAnimationFrame()方法来告诉浏览器哪些javascript代码正在执行,这使得浏览在执行一些代码后得到优化。
mozRequestAnimationFrame()方法接受一个参数,是一个屏幕重绘前被调用的函数。这个函数用来对生成下合适的dom样式的改变,这些改变用在下一次重绘中。你可以像调用setTimeout()一样的方式链式调用mozRequestAnimationFrame(),例如:
function updateProgress(){ var div = document.getElementByIdx_x("status"); div.style.width = (parseInt(div.style.width, 10) + 5) + "%"; if (div.style.left != "100%"){ mozRequestAnimationFrame(updateProgress); } }
mozRequestAnimationFrame(updateProgress);
由于mozRequestAnimationFrame()只运行给定的函数一次,你需要在下一次UI动画的时候再次调用它。你也需要相同的方法来管理何时停止调用。很酷,是非常流畅的动画增强的实例。
因此,mozRequestAnimationFrame()解决了浏览器不知道Javascript动画正在执行和不知道多少才是合适的间隔的问题,但对于不知道何时你的代码才被真正执行,也是由这个方案来解决的。
传递给mozRequestAnimationFrame()的函数实际是一个下一次重绘何时发生的的时间码(以毫秒为单位自1970年1月1日计算)。这是很重要的一点:mozRequestAnimationFrame()实际上列表出将要重绘的点并可以告诉你他们所处的时间。这样你就能够决定怎样更好的来调整你的动画。
为了得到上次重绘过去的时间,你可以查询mozAnimationStartTime,其中包含了过去重绘的时间代码。减去传递回调时的这个值可以计算出下一次重绘到屏幕时所用的时间。使用这些值的典型模式如下:
function draw(timestamp){ //calculate difference since last repaint var diff = timestamp - startTime; //use diff to determine correct next step //reset startTime to this repaint startTime = timestamp; //draw again mozRequestAnimationFrame(draw); } var startTime = mozAnimationStartTime; mozRequestAnimationFrame(draw);
关键是第一次不是通过callback调用时,mozAnimationStartTime是到mozRequestAnimationFrame()经过的时间。如果是在回调函数中,mozAnimationStartTime是通过参数传递进来的时间代码平均值。
webkitRequestAnimationFrame
在很多人热忠于chrome时,随即创建了webkitRequestAnimationFrame()方法。这个版本与firefox的版本在两方面有着细微的差别。一方面,它不通过回调函数传递时间代码,你将无法知道下次重绘何时发生,另一方面,它添加了第二个可选参数来确定哪一个DOM元素发生改变。因此,如果你知道重绘发生在页面哪个部分的元素内,你可以限制重绘发生的区域。
应该不会感到惊讶,有没有相应的mozAnimationStartTime,因为如果没有下一个重绘的时间信息不是很有益。有,只是webkitCancelAnimationFrame()取消了之前计划的重绘。
如果你不需要精确的时间差异,你可以用下面的方式来创建一个用于Firefox4和chrome10+的动画:
function draw(timestamp){ //calculate difference since last repaint var drawStart = (timestamp || Date.now()), diff = drawStart - startTime; //use diff to determine correct next step //reset startTime to this repaint startTime = drawStart; //draw again requestAnimationFrame(draw); } var requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame, startTime = window.mozAnimationStartTime || Date.now(); requestAnimationFrame(draw);
这种模式使用可用的方法来创建以花费多少时间为理念的循环动画。Firefox使用时间代码信息是有用的,而Chrome默认为欠精准的时间对象。当用这种模式的时候,时间的差异给你一种多少时间过去了的想法,但不会告诉你Chrome的下一次重绘出现在何时。不过这比只有多少时间过去了的模糊概念要好些。
总结
mozRequestAnimationFrame()方法的介绍为推动Javascript 动画及web的历史发展有着非常重要的作用。如前所述,JavaScript动画的态几乎和JavaScript的初期一样。随着浏览器逐渐推出CSS transitions 和 animations,很高兴看到基于JavaScript的动画的关注,因为这些在基于的游戏领域将变得更重要和更与CUP联系紧密。知道Javascript何时尝试动画,允许浏览器做更多的优化处理,包括在tab处于后台或移动设备电量过低时停止进程。
该requestAnimationFrame()API现在正由W3C起草一个新议案,并正由Mozilla和Google努力使之成为Web大舞台的一部分。很高兴能看到这两大集团这么迅速的兼容(可能不完全)实现。
RequestAnimFrame使用
对于一个侦中对DOM的所有操作,只进行一次Layout和Paint。
如果发生动画的元素被隐藏了,那么就不再去Paint。
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000/60); }; })(); //调用 function animationLoop(elem){ requestAnimFrame(animationLoop); //logic } Or window.requestAnimFrame = (function(w, r) { w['r'+r] = w['r'+r] || w['webkitR'+r] || w['mozR'+r] || w['msR'+r] || w['oR'+r] || function(c){ w.setTimeout(c, 1000 / 60); }; return w['r'+r]; })(window, 'equestAnimationFrame');
以上通过两段代码示例详解说明了使用requestAnimationFrame实现js动画性能好,希望大家喜欢。