深入理解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 相关文章推荐
jquery remove方法应用详解
Nov 22 Javascript
jQuery弹出(alert)select选择的值
Apr 21 Javascript
很全面的JavaScript常用功能汇总集合
Jan 22 Javascript
Javascript6中字符串的四个新用法分享
Sep 11 Javascript
详谈JavaScript的闭包及应用
Jan 17 Javascript
使用jquery datatable和bootsrap创建表格实例代码
Mar 17 Javascript
js实现登录注册框手机号和验证码校验(前端部分)
Sep 28 Javascript
JavaScript生成指定范围的时间列表
Mar 19 Javascript
React Component存在的几种形式详解
Nov 06 Javascript
layui+SSM的数据表的增删改实例(利用弹框添加、修改)
Sep 27 Javascript
JS+CSS+HTML实现“代码雨”类似黑客帝国文字下落效果
Mar 17 Javascript
js作用域及作用域链工作引擎
Jul 07 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
PHP使用strstr()函数获取指定字符串后所有字符的方法
2016/01/07 PHP
如何离线执行php任务
2017/02/21 PHP
List Information About the Binary Files Used by an Application
2007/06/18 Javascript
jquery配合css简单实现返回顶部效果
2013/09/30 Javascript
简单时间提示DEMO从0开始一直进行计时
2013/11/19 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
2014/02/21 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
JavaScript子窗口调用父窗口变量和函数的方法
2015/10/09 Javascript
AngularJS 让人爱不释手的八种功能
2016/03/23 Javascript
微信开发 微信授权详解
2016/10/21 Javascript
浅析JavaScript动画模拟拖拽原理
2016/12/09 Javascript
Bootstrap源码学习笔记之bootstrap进度条
2016/12/24 Javascript
js实现下拉框效果(select)
2017/03/28 Javascript
jQuery导航条固定定位效果实例代码
2017/05/26 jQuery
NodeJS链接MySql数据库的操作方法
2017/06/27 NodeJs
AngularJS实现进度条功能示例
2017/07/05 Javascript
解决element-ui中下拉菜单子选项click事件不触发的问题
2018/08/22 Javascript
nodejs遍历文件夹下并操作HTML/CSS/JS/PNG/JPG的方法
2018/11/01 NodeJs
JavaScript 自定义html元素鼠标右键菜单功能
2019/12/02 Javascript
[00:47]DOTA2荣耀之路6:天火,天火!
2018/05/30 DOTA
[41:13]完美世界DOTA2联赛PWL S2 Forest vs Rebirth 第一场 11.20
2020/11/20 DOTA
Python的GUI框架PySide的安装配置教程
2016/02/16 Python
Python实现报警信息实时发送至邮箱功能(实例代码)
2019/11/11 Python
Python限制内存和CPU使用量的方法(Unix系统适用)
2020/08/04 Python
pandas map(),apply(),applymap()区别解析
2021/02/24 Python
HTML5新增加标签和功能概述
2016/09/05 HTML / CSS
AmazeUI 模态窗口的实现代码
2020/08/18 HTML / CSS
华为python面试题
2016/05/03 面试题
鲜果饮品店创业计划书
2014/01/21 职场文书
求职自荐信的格式
2014/04/07 职场文书
留学顾问岗位职责
2014/04/14 职场文书
纪念九一八事变演讲稿1000字
2014/09/14 职场文书
四年级数学上册教学计划
2015/01/20 职场文书
助学感谢信范文
2015/01/21 职场文书
创业计划书之小型广告公司
2019/10/22 职场文书
JavaScript 实现页面滚动动画
2021/04/24 Javascript