深入理解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 相关文章推荐
phpwind放自动注册方法
Dec 02 Javascript
js CSS操作方法集合
Oct 31 Javascript
event.X和event.clientX的区别分析
Oct 06 Javascript
js操作iframe兼容各种主流浏览器示例代码
Jul 22 Javascript
JavaScript中的事件委托及好处
Jul 12 Javascript
jQuery layui常用方法介绍
Jul 25 Javascript
javascript数组遍历的方法实例分析
Sep 13 Javascript
浅谈Node.js 沙箱环境
May 15 Javascript
详解创建自定义的Angular Schematics
Jun 06 Javascript
Postman的下载及安装教程详解
Oct 16 Javascript
JS实现鼠标拖拽盒子移动及右键点击盒子消失效果示例
Jan 29 Javascript
Express 配置HTML页面访问的实现
Nov 01 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
Joomla简单判断用户是否登录的方法
2016/05/04 PHP
DWZ+ThinkPHP开发时遇到的问题分析
2016/12/12 PHP
yii2 开发api接口时优雅的处理全局异常的方法
2019/05/14 PHP
ExtJS 简介 让你知道extjs是什么
2008/12/29 Javascript
jquery 应用代码 方便的排序功能
2010/02/06 Javascript
jquery 插件开发备注
2010/08/27 Javascript
JS获取客户端IP地址、MAC和主机名的7个方法汇总
2014/07/21 Javascript
JavaScript 学习笔记之语句
2015/01/14 Javascript
在Html中使用Requirejs进行模块化开发实例详解
2016/04/15 Javascript
微信小程序中hidden不生效原因的解决办法
2017/04/26 Javascript
关于vue.js发布后路径引用的问题解决
2017/08/15 Javascript
AngularJS实现图片上传和预览功能的方法分析
2017/11/08 Javascript
vue3.0 CLI - 2.4 - 新组件 Forms.vue 中学习表单
2018/09/14 Javascript
layui 图片上传+表单提交+ Spring MVC的实例
2019/09/21 Javascript
JavaScript单线程和任务队列原理解析
2020/02/04 Javascript
node.js基于dgram数据报模块创建UDP服务器和客户端操作示例
2020/02/12 Javascript
[40:17]2018DOTA2亚洲邀请赛 4.5 淘汰赛 LGD vs Liquid 第一场
2018/04/06 DOTA
Python和Java进行DES加密和解密的实例
2018/01/09 Python
用TensorFlow实现戴明回归算法的示例
2018/05/02 Python
python中报错&quot;json.decoder.JSONDecodeError: Expecting value:&quot;的解决
2019/04/29 Python
Python log模块logging记录打印用法解析
2020/01/20 Python
python dumps和loads区别详解
2020/02/04 Python
python列表切片和嵌套列表取值操作详解
2020/02/27 Python
python中JWT用户认证的实现
2020/05/18 Python
澳大利亚在线批发商:Simply Wholesale
2021/02/24 全球购物
社区志愿者心得体会
2014/01/03 职场文书
大专生毕业的自我评价
2014/02/06 职场文书
法学函授自我鉴定
2014/02/06 职场文书
《中国的气候》教学反思
2014/02/23 职场文书
美容院店长岗位职责
2014/04/08 职场文书
群众路线剖析材料(四风)
2014/11/05 职场文书
神龙架导游词
2015/02/11 职场文书
外出考察学习心得体会
2016/01/18 职场文书
nginx处理http请求实现过程解析
2021/03/31 Servers
Java 数据结构七大排序使用分析
2022/04/02 Java/Android
BCL经典机 SONY ICF-5900W电路分析
2022/04/24 无线电