深入理解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 相关文章推荐
改进UCHOME的记录发布,增强可访问性用户体验
Jan 17 Javascript
Node.js和PHP根据ip获取地理位置的方法
Mar 14 Javascript
Js中使用hasOwnProperty方法检索ajax响应对象的例子
Dec 08 Javascript
JavaScript实现级联菜单的方法
Jun 29 Javascript
JS组件Bootstrap实现弹出框效果代码
Apr 26 Javascript
详解百度百科目录导航树小插件
Jan 08 Javascript
vue2笔记 — vue-router路由懒加载的实现
Mar 03 Javascript
bootstrap响应式表格实例详解
May 15 Javascript
jQuery插件artDialog.js使用与关闭方法示例
Oct 09 jQuery
js中addEventListener()与removeEventListener()用法案例分析
Mar 02 Javascript
JS正则表达式验证密码强度
Mar 18 Javascript
基于vue--key值的特殊用处详解
Jul 31 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 删除一个目录及目录下的所有文件的函数代码
2010/05/26 PHP
php 生成自动创建文件夹并上传文件的示例代码
2014/03/07 PHP
ThinkPHP之A方法实例讲解
2014/06/20 PHP
JavaScript中的Array对象使用说明
2011/01/17 Javascript
cookie.js 加载顺序问题怎么才有效
2013/07/31 Javascript
jquery ajax方式直接提交整个表单核心代码
2013/08/15 Javascript
基于jquery插件制作左右按钮与标题文字图片切换效果
2013/11/07 Javascript
jquery预览图片实现鼠标放上去显示实际大小
2014/01/16 Javascript
bootstrap datetimepicker实现秒钟选择下拉框
2017/01/05 Javascript
使用JavaScript进行表单校验功能
2017/08/01 Javascript
vuex实现登录状态的存储,未登录状态不允许浏览的方法
2018/03/09 Javascript
微信小程序实现全局搜索代码高亮的示例
2018/03/30 Javascript
Vue中的v-for指令不起效果的解决方法
2018/09/27 Javascript
vue2.0基于vue-cli+element-ui制作树形treeTable
2019/04/30 Javascript
jQuery实现判断滚动条滚动到document底部的方法分析
2019/08/27 jQuery
node.js中 redis 的安装和基本操作示例
2020/02/10 Javascript
ES6中Promise的使用方法实例总结
2020/02/18 Javascript
JavaScript对象字面量和构造函数原理与用法详解
2020/04/18 Javascript
vue开发简单上传图片功能
2020/06/30 Javascript
python使用装饰器和线程限制函数执行时间的方法
2015/04/18 Python
Python构造自定义方法来美化字典结构输出的示例
2016/06/16 Python
Python实现FTP上传文件或文件夹实例(递归)
2017/01/16 Python
你真的了解Python的random模块吗?
2017/12/12 Python
Python 实现交换矩阵的行示例
2019/06/26 Python
Python爬虫:将headers请求头字符串转为字典的方法
2019/08/21 Python
Django 项目布局方法(值得推荐)
2020/03/22 Python
SQL里面如何插入自动增长序列号字段
2012/03/29 面试题
《生命的药方》教学反思
2014/04/08 职场文书
学生会竞选演讲稿学习部
2014/08/25 职场文书
学生检讨书怎么写?
2014/10/10 职场文书
学生旷课检讨书500字
2014/10/28 职场文书
小学语文教师年度考核个人总结
2015/02/05 职场文书
万能检讨书开头与结尾怎么写
2015/02/17 职场文书
Spring boot应用启动后首次访问很慢的解决方案
2021/06/23 Java/Android
Go 语言中 20 个占位符的整理
2021/10/16 Golang
Apache Hudi的多版本清理服务彻底讲解
2022/03/31 Servers