深入理解requestAnimationFrame的动画循环
一、初识requestAnimationFrame
requestAnimationFrame解决了浏览器不知道javascript动画什么时候开始、不知道最佳循环间隔时间的问题。它是跟着浏览器的绘制走的,如果浏览器绘制间隔是16.7ms,它就按这个间隔绘制;如果浏览器绘制间隔是10ms,它就按10ms绘制。这样就不会存在过度绘制的问题,动画不会丢帧。
内部是这么运作的:
浏览器页面每次要重绘,就会通知requestAnimationFrame;
这是资源非常高效的一种利用方式。
怎么讲呢?
有以下两点:
1、就算很多个requestAnimationFrame()要执行,浏览器只要通知一次就可以了。而setTimeout是多个独立绘制。
2、一旦页面不出于当前页面(比如:页面最小化了),页面是不会进行重绘的,自然requestAnimationFrame也不会触发(因为没有通知)。页面绘制全部停止,资源高效利用。
编
二.动画的循环间隔
编写动画循环的关键,是要知道延迟时间多长合适。一方面,循环时间必须足够短,这样才能保证动画效果更平滑流畅;另一方面,循环还要足够长,这样才能保证浏览器有能力渲染产生的变化。大多数显示器的刷新频率是60Hz,相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过了这个频率,用户体验也不会有提升。
因此最平滑动画的最佳循环间隔是1000ms/60,约等于17ms。以这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。
虽然与使用多组setTimeout()相比,使用setInterval()的动画循环效率更高。但是无论setTimeout()还是setInterval()都不十分精确。为它们传入的第二个参数,实际上只是指定了把动画代码添加到浏览器UI线程队列以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务执行完成后再执行。如果UI线程繁忙,比如忙于处理用户操作,那么即使把代码加入队列也不会立即执行。
因此,知道什么时候绘制下一帧是保证动画平滑的关键。然而,面对不十分精确的setTimeout()和setInterval(),开发人员至今都没有办法确保浏览器按时绘制下一帧。
以下是几个浏览器的计时器精度:
IE8及其以下版本浏览器:15.6ms;
IE9及其以上版本浏览器:4ms;
Firefox和Safari:10ms;
Chrome:4ms。
更为复杂的是,浏览器开始限制后台标签页或不活动标签页的计数器。因此,即使你优化了循环间隔,可能仍然只能接近你想要的效果。
三.requestAnimationFrame()
Mozilla的RobertO'Callahan指出,CSS变换动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环间隔,在适当的时候刷新UI。而对于JavaScript动画,浏览器就无从知晓什么时候开始。
因此RobertO'Callahan的方案是,创建一个新方法mozRequestAnimationFrame(),通过它告诉浏览器某些代码将要执行动画。这样浏览器可以在运行某些代码后进行适当的优化。
与setTimeout()和setInterval()方法不同,requestAnimationFrame()不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。
requestAnimationFrame()方法接收一个参数,即在重绘屏幕前调用以个函数。这个函数负责改变下一次重绘时的DOM样式。为了创建动画循环,可以像使用setTimeout()一样,把多个对requestAnimationFrame()的调用连缀起来。
如:
functiondrawFrame(){ window.requestAnimationFrame(drawFrame); //animationcode... } window.requestAnimationFrame(drawFrame);
三.requestAnimationFrame()的兼容性
3.1requestAnimationFrame()的兼容性封装:
由于mozRequestAnimationFrame()是HTML5的新功能,目前各大浏览器的支持情况各异。如果希望代码具备更好的跨平台性,可以考虑使用下面的代码实现各平台兼容性:
if(!window.requestAnimationFrame){ window.requestAnimationFrame=(window.webkitRequestAnimationFrame|| window.mozRequestAnimationFrame|| window.oRequestAnimationFrame|| window.msRequestAnimationFrame|| function(callback){ varself=this,start,finish; returnwindow.setTimeout(function(){ start=+newDate(); callback(start); finish=+newDate(); self.timeout=1000/60-(finish-start); },self.timeout); }); }
这段代码先检查了window.requestAnimationFrame函数的定义是否存在。如果不存在,就遍历已知的各种浏览器实现并替代该函数。如果还是找不到一个与浏览器相关的实现,它最终会采用基于JavaScript定时器的动画以每秒60帧的间隔调用setTimeout函数。
mozRequestAnimationFrame()会接收一个时间码(从1970年1月1日起至今的毫秒数),表示下一次重绘的实际发生时间。这样,mozRequestAnimationFrame()就会根据这个时间码设定将来的某个时刻进行重绘。
但是webkitRequestAnimationFrame()和msRequestAnimationFrame()不会给回调函数传递时间码,因此无法知道下一次重绘将发生在什么时间。
如果要计算两次重绘的时间间隔,Firefox中可以使用既有的时间码,而在Chrome和IE则可以使用不太精确地Date()对象。
3.2cancelRequestAnimFrame()的兼容性封装:
W3C也提供了cancelRequestAnimationFrame()方法,用于取消回调函数。requestAnimationFrame()方法会返回一个对象,用做标识回掉函数身份。若要取消回调函数的执行,可将其传给cancelRequestAnimationFrame()。
window.cancelRequestAnimFrame=(function(){ returnwindow.cancelAnimationFrame|| window.webkitCancelRequestAnimationFrame|| window.mozCancelRequestAnimationFrame|| window.oCancelRequestAnimationFrame|| window.msCancelRequestAnimationFrame|| clearTimeout; })();
3.3requestAnimationFrame()升级版封装方法:
另外还有一种更优雅的requestAnimationFrame()的兼容性封装方法:
(function(){ varlastTime=0; varvendors=['ms','moz','webkit','o']; for(varx=0;x<vendors.length&&!window.requestAnimationFrame;++x){ window.requestAnimationFrame=window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame=window[vendors[x]+'CancelAnimationFrame']||window[vendors[x]+'CancelRequestAnimationFrame']; } if(!window.requestAnimationFrame) window.requestAnimationFrame=function(callback,element){ varcurrTime=newDate().getTime(); vartimeToCall=Math.max(0,16-(currTime-lastTime)); varid=window.setTimeout(function(){callback(currTime+timeToCall);}, timeToCall); lastTime=currTime+timeToCall; returnid; }; if(!window.cancelAnimationFrame) window.cancelAnimationFrame=function(id){ clearTimeout(id); }; }());
总结
以上就是这篇文章的全部内容,希望能对大家的学习或者工作带来一定的帮助,如果有疑问大家可以留言交流。