深入理解requestAnimationFrame的动画循环

深入理解requestAnimationFrame的动画循环

本文首先介绍什么是requestAnimationFrame,然后深入讲述requestAnimationFrame的动画循环。文章很详细,相信对大家学习requestAnimationFrame有一定的参考价值。有需要就来看看吧。

一、初识requestAnimationFrame

RequestAnimationFrame解决了浏览器不知道javascript动画何时开始以及最佳循环间隔的问题。它遵循浏览器的绘图。如果浏览器的绘制间隔是16.7ms,就在这个间隔进行绘制;如果浏览器绘制间隔为10ms,则绘制间隔为10ms。这样就不会出现过画的问题,动画也不会丢帧。

下面是它的内部工作原理:

浏览器每次重绘页面,都会通知requestAnimationFrame

这是一种非常有效的资源利用方式。

怎么说呢?

有以下两点:

1.即使要执行许多requestAnimationFrame(),浏览器也只需要通知它们一次。而setTimeout是一些独立的绘图。

2.一旦页面不在当前页面之外(比如页面被最小化),页面就不会被重绘,自然也不会触发requestAnimationFrame(因为没有通知)。所有页面绘制停止和资源都得到有效利用。

编辑

二. 动画的循环间隔

写动画循环的关键是知道延迟时间多长合适。一方面,循环时间必须足够短,才能保证动画效果更加流畅;另一方面,循环应该足够长,以确保浏览器能够呈现更改。大多数显示器的刷新频率为60Hz,相当于每秒60次重绘。大多数浏览器限制重绘操作不超过显示器的重绘频率,因为即使超过这个频率,用户体验也不会得到改善。

所以最流畅动画的最佳循环间隔是1000ms/60,大约是17 ms,在这个循环间隔重绘的动画是流畅的,因为这个速度最接近浏览器的最大速度限制。为了适应17ms的循环间隔,可能需要对多个动画进行约束,以免结束得太快。

虽然与使用多组setTimeout()相比,在动画循环中使用setInterval()效率更高。但是setTimeout()和setInterval()都不是很准确。它们传入的第二个参数实际上只指定了动画代码加入浏览器UI线程队列等待执行的时间。如果队列前面已经添加了其他任务,动画代码将在前面的任务完成后执行。如果UI线程很忙,比如处理用户操作,即使代码被添加到队列中,也不会立即执行。

因此,知道何时绘制下一帧是保证动画流畅的关键。但是,面对不精确的setTimeout()和setInterval(),开发者仍然无法保证浏览器按时绘制下一帧。

以下是几个浏览器的计时器精度:

E8及以下浏览器:15.6ms;

IE9及以上浏览器:4 ms

而火狐:10ms

铬:4毫秒.

更复杂的是,浏览器开始限制背景标签或者非活动标签的计数器。因此,即使你优化了周期间隔,你可能仍然只能达到预期的效果。

三. requestAnimationFrame()

Mozilla的罗伯特奥卡拉汉(Robert OCallahan)指出,CSS变换动画的优势在于浏览器知道动画何时开始,因此会计算出正确的循环间隔,并在适当的时候刷新UI。对于JavaScript动画,浏览器无法知道何时开始。

因此,罗伯特奥卡拉汉的计划是创建一个新方法mozRequestAnimationFrame(),它可以告诉浏览器一些代码将被动画化。这样浏览器在运行一些代码后就可以进行适当的优化。

与setTimeout()和setInterval()方法不同,requestAnimationFrame()不需要调用者指定帧率,浏览器会自行决定最佳帧效率。

requestAnimationFrame()方法接收一个参数,即在重绘屏幕前调用以个函数。这个函数负责改变下一次重绘时的数字正射影像图样式。为了创建动画循环,可以像使用setTimeout()一样,把多个对requestAnimationFrame()的调用连缀起来。

如:

函数drawFrame() {

窗户。requestanimationframe(绘制帧);

//动画代码.

}

窗户。requestanimationframe(绘制帧);

三. requestAnimationFrame()的兼容性

3.1 requestAnimationFrame()的兼容性封装:

由于mozRequestAnimationFrame()是HTML5的新功能,目前各大浏览器的支持情况各异。如果希望代码具备更好的跨平台性,可以考虑使用下面的代码实现各平台兼容性:

如果(!window.requestAnimationFrame) {

窗户。requestanimationframe=(window。webkitrequestanimationframe | |

窗户。mozrequestanimationframe | |

窗户。orequestanimationframe | |

窗户。msrequestanimationframe | |

函数(回调){

var self=this,start,finish

返回窗口。settimeout(function(){

start=new Date();

回调(开始);

finish=new Date();

self.timeout=1000/60 -(完成-开始);

},自我。暂停);

});

}

这段代码先检查了window.requestAnimationFrame函数的定义是否存在。如果不存在,就遍历已知的各种浏览器实现并替代该函数。如果还是找不到一个与浏览器相关的实现,它最终会采用基于Java脚本语言定时器的动画以每秒60帧的间隔调用设置超时函数。

mozRequestAnimationFrame()会接收一个时间码(从1970年一月一日起至今的毫秒数),表示下一次重绘的实际发生时间。这样,mozRequestAnimationFrame()就会根据这个时间码设定将来的某个时刻进行重绘。

但是webkitRequestAnimationFrame()和msRequestAnimationFrame()不会给回调函数传递时间码,因此无法知道下一次重绘将发生在什么时间。

如果要计算两次重绘的时间间隔,火狐中可以使用既有的时间码,而在铬和工业管理学(工业工程)则可以使用不太精确地日期()对象。

3.2 cancelRequestAnimFrame()的兼容性封装:

万维网路联盟(环球网Consortium简称W3C)也提供了cancelRequestAnimationFrame()方法,用于取消回调函数requestAnimationFrame()方法会返回一个对象,用做标识回掉函数身份。若要取消回调函数的执行,可将其传给cancelRequestAnimationFrame()。

窗户。cancelrequestanimframe=(function(){

返回窗口。cancelanimationframe | |

窗户。webkitcancelrequestanimationframe | |

窗户。mozcancelrequestanimationframe | |

窗户。ocancelrequestanimationframe | |

窗户。mscancelrequestanimationframe | |

清除超时

} )();

3.3 requestAnimationFrame()升级版封装方法:

另外还有一种更优雅的requestAnimationFrame()的兼容性封装方法:

(函数(){

var last time=0;

var vendors=[ms , moz , webkit , o ];

for(var x=0;x厂商。长度!window . requestanimationframex){

窗户。RequestAnimationFrame=window[vendors[x] RequestAnimationFrame ];

窗户。CancelAnimationFrame=window[vendors[x] CancelAnimationFrame ]| | window[vendors[x] CancelRequestAnimationFrame ];

}

如果(!window.requestAnimationFrame)

窗户。requestanimationframe=function(callback,element) {

var currTime=新日期()。getTime();

var timeToCall=Math.max(0,16-(当前时间-上次时间));

var id=窗口。settimeout(function(){ callback(curr time要调用的时间);},

打电话的时间);

last time=curr time调用时间;

返回id;

};

如果(!window.cancelAnimationFrame)

窗户。cancelanimationframe=函数(id){

清除超时(id);

};

}());

总结

以上就是这篇文章的全部内容,希望能对大家的学习或者工作带来一定的帮助,如果有疑问大家可以留言交流。

深入理解requestAnimationFrame的动画循环