缓动函数requestAnimationFrame 更好的实现浏览器经动画


Posted in Javascript onDecember 07, 2012

写缓动函数用到requestAnimationFrame函数,之前了解过一些,但总觉得又不是很了解,所以翻译一篇老外的文章,以便学习分享。

requestAnimationFrame是什么?
以前我们做动画需要一个定时器,每间隔多少毫秒就做出一些改变。现在有个好消息:浏览器厂商已经决定提供一个专门做动画的方法,即requestAnimationFrame(),而且基于浏览器的层面也能更好的进行优化。但是呢,这只是一个做动画的基础API,即不基于DOM元素的style变化,也不基于canvas,或者WebGL。所以,具体的动画细节需要我们自己写。

我们为什么要用它?
对于同时进行的n个动画,浏览器能够进行优化,把原本需要N次reflow和repaint优化成1次,这样就实现了高质量的动画。举个例子,现在有基于JS的动画,还有基于CSS的transitions,或者SVG SMIL. Plus,如果浏览器的某个tab正在运行这样一个动画,然后你切到另一个tab,或者干脆最小化,总之就是你看不见它了,这时浏览器就会停止动画。这将意味着更少的CPU,GPU和更少的内存消耗,这样电池的使用寿命就大大延长了。

如何使用它?

// shim layer with setTimeout fallback 
window.requestAnimFrame = (function(){ 
return window.requestAnimationFrame || 
window.webkitRequestAnimationFrame || 
window.mozRequestAnimationFrame || 
window.oRequestAnimationFrame || 
window.msRequestAnimationFrame || 
function(/* function */ callback, /* DOMElement */ element){ 
window.setTimeout(callback, 1000 / 60); 
}; 
})(); 
// usage: 
// instead of setInterval(render, 16) .... 
(function animloop(){ 
render(); 
requestAnimFrame(animloop, element); 
})();

注意:这里我使用了“requestAnimFrame”,因为规范仍在不断变化中,我并不想任由规范摆布。
requestAnimationFrame API
window.requestAnimationFrame(function(/* time */ time){ 
// time ~= +new Date // the unix time 
}, /* optional bounding elem */ elem);

先给出Chrome和Firefox的版本
window.mozRequestAnimationFrame([callback]); // Firefox 
window.webkitRequestAnimationFrame(callback[, element]); // Chrome

参数:
callback:(FF可选,Chrome必选)

下次repaint调用的函数,函数的第一个参数是当前时间
element:(FF无)

意译一下吧:其实就是画布了,而那个‘画',是动画。(the element that visually bounds the entire animation)。对canvas和WebGL来说,它就是<canvas>元素,对于DOM节点来说,你可以不管它,如果你想稍微进行一下优化,也可以传个参数进来。

它到底靠不靠谱啊?
现在,Webkit实现(Nightly Safari 和 Chrome Dev Channel 可用)和Mozilla实现(FF4可用)有一些的差异,Mozilla的实现有一个Bug。事实上,FF动画的帧数是这么算的:1000/(16 + N) fps,其中N是callback的执行时间,单位为毫秒。如果你的callback执行时间为1000ms,那么它最高的帧数也就只有1fps。如果你的callback执行时间为1ms,那么帧数差不多就是60fps。这个bug肯定会被修复,也许就是FF4的下一个版本吧。Chrome10没有time参数(added in m11.弱弱的问下,m11是什么?),FF目前没有element参数。
我看了下火狐的那个bug,大概就是说:
FF的mozRequestAnimationFrame()永远不可能达到60fps,即使你的callback执行时间小于1000/60毫秒。举个例子:

function callback(time) { 
window.mozRequestAnimationFrame(callback); 
doWork(); 
}

如果doWork()耗时1000/60毫秒,那么帧数大约是30fps,而同样的动画如果使用setTimeout(callback, 16),帧速则是60fps。似乎callback总是在callback执行完毕后的大约16ms再次开始执行,而不是在callback开始执行后的16ms再次开始执行,如果是后者,且计算又够快的话,就能产生60fps的帧数。
如果你是规范控,传送门在此

话不多说,首先来个经典的动画函数

function animate(element, name, from, to, time) { 
time = time || 800; //默认0.8秒 
var style = element.style, 
latency = 60, // 每60ms一次变化 
count = time / latency, //变化的次数 
step = Math.round((to - from) / count), //每一步的变化量 
now = from; 
function go() { 
count--; 
now = count ? now + step : to; 
style[name] = now + 'px'; 
if (count) { 
setTimeout(go, latency); 
} 
} 
style[name] = from + 'px'; 
setTimeout(go, latency); 
}

姑且不论这个函数的设计存在局限性,如只能对以px为单位的样式进行修改。仅从函数的实现上来看,这可以是一个非常经典的动画理念,其基本逻辑由以下部分组成:
获取起点值from和终点值to,通过动画需要进行的时间time,以及每侦间隔latency的要求,计算出值的改变次数count和每次改变的量step。
开启setTimeout(fn, latency);来步进到下一侦。

在下一侦中,设置属性步进一次,如果动画还没结束,再回到第2步继续下一侦。
这个函数工作得很好,服务了千千万万的站点和系统,事实上jQuery的animate函数的核心也无非是setInterval函数。
但是,随着现在系统复杂度的稳步上升,动画效果也越来越多,同时对动画的流畅度也有了更多的重视,这导致上面的函数会出现一些问题。例如同时打开100个动画效果,根据上面的函数,很明显会有100个定时器在同时运行,这些定时器之间的调度会对性能有轻微的影响。虽然在正常的环境中,这些许的影响并不会有什么关系,但是在动画这种对流畅度有很高要求的环境下,任何细微的影响都可能产生出不好的用户体验。

在这样的情况下,有一些开发者就发明了一种基于统一帧管理的动画框架,他使用一个定时器触发动画帧,不同的动画来注册这些帧,在每一帧上处理多个动画的属性变化。这样的好处是减少了定时器调度的开销,但是对于动画框架的开发者来说,统一帧管理、提供监听帧的API等,都是需要开发和维护的。

浏览器的直接支持
最终,浏览器厂商们发现这件事其实可以由他们来做,并且基于浏览器层面,还可以有更多的优化,比如:
对于一个侦中对DOM的所有操作,只进行一次Layout和Paint。
如果发生动画的元素被隐藏了,那么就不再去Paint。
于是,浏览器开始推出一个API,叫做requestAnimationFrame,关于这个函数,MDC的相关页面有比较详细的介绍,简单来说,这个函数有2种使用方法:
调用requestAnimationFrame函数,传递一个callback参数,则在下一个动画帧时,会调用callback。
不传递参数地直接调用该函数,启动动画帧,下一个帧触发时,会同时触发window.onmozbeforepaint事件,可以通过注册该事件来进行动画。

第2种方法由于依赖于Firefox自己的事件,且beforepaint事件还没进入到标准中,所以不推荐使用,还是使用第1种方式比较好。此时,我们的动画逻辑可以变成这样:
记录当前时间startTime,作为动画开始的时间。
请求下一帧,带上回调函数。
下一帧触发时,回调函数的第一个参数为当前的时间,再与startTime进行比较,确定时间间隔ellapseTime。
判断ellapseTime是否已经超过事先设定的动画时间time,如果超过,则结束动画。
计算动画属性变化的差值differ = to - from,再确定在ellapseTime的时候应该变化多少step = differ / time * ellapseTime。
计算出现在应该变化到的位置Math.round(from + step),并重新对样式赋值。
继续请求下一帧。

新的动画函数
下面就是一个全新的动画函数:

function animate(element, name, from, to, time) { 
time = time || 800; // 默认0.8秒 
var style = element.style, 
startTime = new Date; 
function go(timestamp) { 
var progress = timestamp - startTime; 
if (progress >= duration) { 
style[name] = to + 'px'; 
return; 
} 
var now = (to - from) * (progress / duration); 
style[name] = now.toFixed() + 'px'; 
requestAnimationFrame(go); 
} 
style[name] = from + 'px'; 
requestAnimationFrame(go); 
}

到这一步,还剩一个问题,那就是并不是每个浏览器都支持requestAnimationFrame函数的,所以再做一个简单的修正。
根据Firefox的特性来看,其mozRequestAnimationFrame提供的最高FPS为60,并且会根据每一帧的计算的耗时来进行调整,比如每一帧计算用了1s,那他只会提供1FPS的动画效果。
而Chrome的高版本同样也实现了这个函数,叫webkitRequestAnimationFrame,可以预见未来还会有Opera的oRequestAnimationFrame和IE的msRequestAnimationFrame,所以这里一并做一个简单的兼容处理:
requestAnimationFrame = window.requestAnimationFrame || 
window.mozRequestAnimationFrame || 
window.webkitRequestAnimationFrame || 
window.msRequestAnimationFrame || 
window.oRequestAnimationFrame || 
function(callback) { setTimeout(callback, 1000 / 60); };
Javascript 相关文章推荐
通过JAVAScript实现页面自适应
Jan 19 Javascript
在JavaScript的正则表达式中使用exec()方法
Jun 16 Javascript
js实现简洁大方的二级下拉菜单效果代码
Sep 01 Javascript
javascript实现的上下无缝滚动效果
Sep 19 Javascript
jQuery插件HighCharts绘制2D饼图效果示例【附demo源码下载】
Mar 21 jQuery
webpack多入口文件页面打包配置详解
Jan 09 Javascript
JavaScript中字符串的常用操作方法及特殊字符
Mar 18 Javascript
vue计算属性computed、事件、监听器watch的使用讲解
Jan 21 Javascript
微信小程序 textarea 层级过高问题简单解决方案
Oct 14 Javascript
JS数组属性去重并校验重复数据
Jan 10 Javascript
解决vue项目运行提示Warnings while compiling.警告的问题
Sep 18 Javascript
element中table高度自适应的实现
Oct 21 Javascript
javascrpt绑定事件之匿名函数无法解除绑定问题
Dec 06 #Javascript
php图像生成函数之间的区别分析
Dec 06 #Javascript
javascript SpiderMonkey中的函数序列化如何进行
Dec 05 #Javascript
javascript中有趣的反柯里化深入分析
Dec 05 #Javascript
js multiple全选与取消全选实现代码
Dec 04 #Javascript
在js(jquery)中获得文本框焦点和失去焦点的方法
Dec 04 #Javascript
关于javascript中的typeof和instanceof介绍
Dec 04 #Javascript
You might like
《忧国的莫里亚蒂》先导宣传图与STAFF公开
2020/03/04 日漫
使用php实现下载生成某链接快捷方式的解决方法
2013/05/07 PHP
PHP 根据key 给二维数组分组
2016/12/09 PHP
PHP实现深度优先搜索算法(DFS,Depth First Search)详解
2017/09/16 PHP
弹出模态框modal的实现方法及实例
2017/09/19 PHP
分享27个jQuery 表单插件集合推荐
2011/04/25 Javascript
jquery实现每个数字上都带进度条的幻灯片
2013/02/20 Javascript
纯JavaScript实现HTML5 Canvas六种特效滤镜示例
2013/06/28 Javascript
图片放大镜jquery.jqzoom.js使用实例附放大镜图标
2014/06/19 Javascript
js实现简单随机抽奖的方法
2015/01/27 Javascript
JQuery实现可直接编辑的表格
2015/04/16 Javascript
利用Jquery队列实现根据输入数量显示的动画
2016/09/01 Javascript
Vue.js常用指令汇总(v-if、v-for等)
2016/11/03 Javascript
jquery心形点赞关注效果的简单实现
2016/11/14 Javascript
使用Angular缓存父页面数据的方法
2017/01/03 Javascript
RequireJS 依赖关系的实例(推荐)
2017/01/21 Javascript
AngularJS路由实现页面跳转实例
2017/03/03 Javascript
JS正则表达式验证中文字符
2017/05/08 Javascript
layui中layer前端组件实现图片显示功能的方法分析
2017/10/13 Javascript
webpack4简单入门实例
2018/09/06 Javascript
Python实现遍历目录的方法【测试可用】
2017/03/22 Python
Python requests库用法实例详解
2018/08/14 Python
Django多数据库配置及逆向生成model教程
2020/03/28 Python
Python3.7在anaconda里面使用IDLE编译器的步骤详解
2020/04/29 Python
python 装饰器的基本使用
2021/01/13 Python
python上下文管理的使用场景实例讲解
2021/03/03 Python
Mamas & Papas沙特阿拉伯:英国最受欢迎的婴儿品牌
2017/11/20 全球购物
大学生个人求职口试自我评价
2014/02/16 职场文书
需求分析说明书
2014/05/09 职场文书
责任担保书范文
2014/05/21 职场文书
525心理活动总结
2014/07/04 职场文书
岗位安全生产责任书
2014/07/28 职场文书
2015年保险业务员工作总结
2015/05/27 职场文书
教师个人教学反思
2016/02/23 职场文书
2016简单的租房合同范本
2016/03/18 职场文书
公司员工违法违章行为检讨书
2019/06/24 职场文书