缓动函数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 相关文章推荐
jquery 日期分离成年月日的代码
May 14 Javascript
javascript 运算数的求值顺序
Aug 23 Javascript
分享一个自己写的table表格排序js插件(高效简洁)
Oct 29 Javascript
如何在node的express中使用socket.io
Dec 15 Javascript
JS插件overlib用法实例详解
Dec 26 Javascript
jquery网页日历显示控件calendar3.1使用详解
Nov 24 Javascript
JavaScript与JQUERY获取元素的宽、高和位置
Feb 26 Javascript
原生js仿浏览器滚动条效果
Mar 02 Javascript
基于canvas粒子系统的构建详解
Aug 31 Javascript
基于vue的短信验证码倒计时demo
Sep 13 Javascript
JS实现百度网盘任意文件强制下载功能
Aug 31 Javascript
详解vue-cli中使用rem,vue自适应
May 06 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
第十节 抽象方法和抽象类 [10]
2006/10/09 PHP
PHP写MySQL数据 实现代码
2009/06/15 PHP
PHP eval函数使用介绍
2013/12/08 PHP
PHP析构函数destruct与垃圾回收机制的讲解
2019/03/22 PHP
jQuery 性能优化指南(2)
2009/05/21 Javascript
jquery 插件开发备注
2010/08/27 Javascript
js中if语句的几种优化代码写法
2011/03/12 Javascript
javascript重写alert方法的实例代码
2013/03/29 Javascript
js跳转页面方法总结
2014/01/29 Javascript
11种ASP连接数据库的方法
2015/09/18 Javascript
jQuery仅用3行代码实现的显示与隐藏功能完整实例
2015/10/08 Javascript
D3.js实现散点图和气泡图的方法详解
2016/09/21 Javascript
jQuery实现选中行变色效果(实例讲解)
2017/07/06 jQuery
JavaScript调试之console.log调试的一个小技巧分享
2017/08/07 Javascript
JS设计模式之惰性模式(二)
2017/09/29 Javascript
微信开发之微信jssdk录音功能开发示例
2018/10/22 Javascript
js 计算图片内点个数的示例代码
2019/04/04 Javascript
js单线程的本质 Event Loop解析
2019/10/29 Javascript
javascript设计模式 ? 解释器模式原理与用法实例分析
2020/04/17 Javascript
python 生成图形验证码的方法示例
2018/11/11 Python
python实现控制电脑鼠标和键盘,登录QQ的方法示例
2019/07/06 Python
简单了解python反射机制的一些知识
2019/07/13 Python
matlab、python中矩阵的互相导入导出方式
2020/06/01 Python
浅析Python 简单工厂模式和工厂方法模式的优缺点
2020/07/13 Python
HTML5制作表格样式
2016/11/15 HTML / CSS
Origins悦木之源香港官网:雅诗兰黛集团高端植物护肤品牌
2018/03/21 全球购物
汇科协同Java笔试题
2012/03/31 面试题
经理管理专业自荐信范文
2013/12/31 职场文书
集团薪酬管理制度
2014/01/13 职场文书
物业管理专业求职信
2014/06/11 职场文书
龙门石窟导游词
2015/02/02 职场文书
2016年社区植树节活动总结
2016/03/16 职场文书
2019最新公司租房合同(例文)
2019/07/18 职场文书
写给消防战士们的一封慰问信
2019/10/07 职场文书
Vue中插槽slot的使用方法与应用场景详析
2021/06/08 Vue.js
vue中data里面的数据相互使用方式
2022/06/05 Vue.js