深入理解requestAnimationFrame的动画循环


Posted in Javascript onSeptember 20, 2016

一、初识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的 Robert O'Callahan 指出,CSS变换动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环间隔,在适当的时候刷新UI。而对于JavaScript动画,浏览器就无从知晓什么时候开始。

因此Robert O'Callahan的方案是,创建一个新方法mozRequestAnimationFrame() ,通过它告诉浏览器某些代码将要执行动画。这样浏览器可以在运行某些代码后进行适当的优化。

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

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

如:

function drawFrame() {
 window.requestAnimationFrame(drawFrame);
 // animation code...
}
window.requestAnimationFrame(drawFrame);

三. requestAnimationFrame()的兼容性

3.1 requestAnimationFrame()的兼容性封装:

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

if(!window.requestAnimationFrame) {
 window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
 window.mozRequestAnimationFrame ||
 window.oRequestAnimationFrame ||
 window.msRequestAnimationFrame ||
 function(callback) {
  var self = this, start, finish;
  return window.setTimeout(function() {
   start = +new Date();
   callback(start);
   finish = +new Date();
   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.2 cancelRequestAnimFrame()的兼容性封装:

W3C也提供了cancelRequestAnimationFrame()方法,用于取消回调函数。requestAnimationFrame()方法会返回一个对象,用做标识回掉函数身份。若要取消回调函数的执行,可将其传给cancelRequestAnimationFrame()

window.cancelRequestAnimFrame = ( function() {
 return window.cancelAnimationFrame ||
  window.webkitCancelRequestAnimationFrame ||
  window.mozCancelRequestAnimationFrame ||
  window.oCancelRequestAnimationFrame ||
  window.msCancelRequestAnimationFrame ||
  clearTimeout;
} )();

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

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

(function() {
 var lastTime = 0;
 var vendors = ['ms', 'moz', 'webkit', 'o'];
 for(var x = 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) {
   var currTime = new Date().getTime();
   var timeToCall = Math.max(0, 16 - (currTime - lastTime));
   var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
    timeToCall);
   lastTime = currTime + timeToCall;
   return id;
  };

 if (!window.cancelAnimationFrame)
  window.cancelAnimationFrame = function(id) {
   clearTimeout(id);
  };
}());

总结

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

Javascript 相关文章推荐
JavaScript调试技巧之console.log()详解
Mar 19 Javascript
js给selected添加options的方法
May 06 Javascript
jQuery实现的仿百度分页足迹效果代码
Oct 30 Javascript
在JavaScript中对HTML进行反转义详解
May 18 Javascript
第一次接触神奇的Bootstrap菜单和导航
Aug 01 Javascript
JavaScript获取当前时间向前推三个月的方法示例
Feb 04 Javascript
正则验证小数点后面只能有两位数的方法
Feb 28 Javascript
纯原生js实现贪吃蛇游戏
Apr 16 Javascript
详解layui弹窗父子窗口之间传参数的方法
Jan 16 Javascript
JS二级菜单不同实现方法分析【4种方法】
Dec 21 Javascript
vue.js实现三级菜单效果
Oct 19 Javascript
Vue自定义全局弹窗组件操作
Aug 11 Javascript
javascript cookie用法基础教程(概念,设置,读取及删除)
Sep 20 #Javascript
谈谈对JavaScript原生拖放的深入理解
Sep 20 #Javascript
Javascript获取图片原始宽度和高度的方法详解
Sep 20 #Javascript
AngularJS 过滤器(自带和自建)详解
Sep 19 #Javascript
js Canvas实现圆形时钟教程
Sep 19 #Javascript
Bootstrap模态框调用功能实现方法
Sep 19 #Javascript
javascript实现的上下无缝滚动效果
Sep 19 #Javascript
You might like
mysql+php分页类(已测)
2008/03/31 PHP
关于php支持分块与断点续传文件下载功能代码
2014/05/09 PHP
PHP实现数组根据某个单元字段排序操作示例
2018/08/01 PHP
Yii2框架自定义验证规则操作示例
2019/02/08 PHP
javascript 实现父窗口引用弹出窗口的值的脚本
2007/08/07 Javascript
将CKfinder整合进CKEditor3.0的新方法
2010/01/10 Javascript
jquery验证手机号码、邮箱格式是否正确示例代码
2013/07/28 Javascript
js代码实现的加入收藏效果并兼容主流浏览器
2014/06/23 Javascript
JavaScript中Cookies的相关使用教程
2015/06/04 Javascript
jQuery插件EnPlaceholder实现输入框提示文字
2015/06/05 Javascript
QQ登录背景闪动效果附效果演示源码下载
2015/09/22 Javascript
JavaScript简单实现弹出拖拽窗口(二)
2016/06/17 Javascript
js转换对象为xml
2017/02/17 Javascript
vue实现手机号码抽奖上下滚动动画示例
2017/10/18 Javascript
vue+vuecli+webpack中使用mockjs模拟后端数据的示例
2017/10/24 Javascript
jQuery实现提交表单时不提交隐藏div中input的方法
2019/10/08 jQuery
[01:03:36]DOTA2-DPC中国联赛 正赛 VG vs Magma BO3 第二场 1月26日
2021/03/11 DOTA
Python实现telnet服务器的方法
2015/07/10 Python
Python上传package到Pypi(代码简单)
2016/02/06 Python
Sanic框架蓝图用法实例分析
2018/07/17 Python
python获取时间及时间格式转换问题实例代码详解
2018/12/06 Python
Python3.6.2调用ffmpeg的方法
2019/01/10 Python
Python+OpenCV+pyQt5录制双目摄像头视频的实例
2019/06/28 Python
python使用if语句实现一个猜拳游戏详解
2019/08/27 Python
什么是Python中的顺序表
2020/06/02 Python
Pytorch损失函数nn.NLLLoss2d()用法说明
2020/07/07 Python
Dr. Martens马汀博士官网:马丁靴始祖品牌
2016/10/15 全球购物
Elemis美国官网:英国的第一豪华护肤品牌
2018/03/15 全球购物
PHP解析URL是哪个函数?怎么用?
2013/05/09 面试题
视图的作用
2014/12/19 面试题
init进程的作用
2012/04/12 面试题
专科文秘应届生求职信
2013/11/18 职场文书
司马光教学反思
2014/02/01 职场文书
医院学雷锋活动策划方案
2014/02/15 职场文书
2015年企业团支部工作总结
2015/05/21 职场文书
php解析非标准json、非规范json的方式实例
2022/05/10 PHP