❗ 本文最后更新于 4934 天前,文中所描述的信息可能已发生改变,请谨慎使用。
Web中的JS动画原理并不复杂,JK前些时写过系列文章《动画组件(wagang版)》,讲解帧动画的原理以及QWrap版的实现,偷懒直接贴过来:
我们把动画抽象一下,它由以下几个组成部分: 时长(dur),即动画播放的时间总长。 进度(per),播放的进度,在区间[0,1]之内。 帧间隔时间(frameTime),即多长时间播放一帧。 动画函数(animFun),它是每一帧的渲染函数。定时器每隔frameTime来调用一下animFun(per)。也就是说按进度播放一帧动画。 这四个基本参数将动画抽象成为一个js类,系统的理解就是: “在dur时间内,每隔frameTime时间,播放一次animFun(per)。”
随着页面上动画的使用越来越频繁,为了避免过多的定时器带来额外系统开销,一般的动画组件都会采用“统一帧管理”:只使用一个定时器,在每一动画帧依次调用所有注册的动画事件。
后来,一些浏览器推出各自私有Api提供动画管理,W3C也出了一份WindowAnimationTiming interface规范,定义了以下接口:
[Supplemental, NoInterfaceObject] interface WindowAnimationTiming { long requestAnimationFrame(in FrameRequestCallback callback); void cancelRequestAnimationFrame(in long handle); };
Window implements WindowAnimationTiming;
[Callback, NoInterfaceObject] interface FrameRequestCallback { void sample(in DOMTimeStamp time); };
注册动画使用requestAnimactionFrame函数,接受动画函数callback作为参数,并返回动画ID;移除动画使用cancelRequestAnimationFrame函数,参数是动画ID;动画函数执行时,会传入当前系统时间戳。
对比文首JK总结的动画四要素,规范里少了一个帧间隔时间frameTime。这正是动画组件可以被优化的地方,原生实现里的frameTime被设计为可根据CPU使用率、window是否被最小化、元素是否被隐藏等因素进行动态调整。浏览器可根据实际情况降低fps甚至停止动画。来看下新Api怎么用。
高版本webkit内核提供的webkitRequestAnimationFrame,跟规范非常接近,看一个示例:
<script>
/*webkit only*/
var animationStartTime,requestID,
dur = 3 * 1000;
function animate(time) {
var per = Math.min(1.0, (time - animationStartTime) / dur);
if(per >= 1) {
window.webkitCancelRequestAnimationFrame(requestID);
} else {
document.getElementById("animated").style.left = Math.round(500 * per) + "px";
window.webkitRequestAnimationFrame(animate);
}
}
function start() {
animationStartTime = Date.now();
requestID = window.webkitRequestAnimationFrame(animate);
}
</script>
<button onclick="start()">Click me to start!</button>
<div id="animated" style="position: absolute; left: 10px; padding: 50px;background: crimson; color: white">Hello there.</div>
firefox里也有类似的mozRequestAnimationFrame,不过非常坑爹的找不到对应的mozCancelRequestAnimationFrame,幸好可以注册动画时不传动画函数,把动画函数绑定在moz私有的beforepaint事件上,通过移除事件来移除动画:
<script>
/*gecko only*/
var animationStartTime,
dur = 3 * 1000;
function animate(event) {
var time = event.timeStamp;
var per = Math.min(1.0, (time - animationStartTime) / dur);
if(per >= 1) {
window.removeEventListener('MozBeforePaint', animate, false);
} else {
document.getElementById("animated").style.left = Math.round(500 * per) + "px";
window.mozRequestAnimationFrame();
}
}
function start() {
animationStartTime = Date.now();
window.addEventListener('MozBeforePaint', animate, false);
window.mozRequestAnimationFrame();
}
</script>
<button onclick="start()">Click me to start!</button>
<div id="animated" style="position: absolute; left: 10px; padding: 50px;background: crimson; color: white">Hello there.</div>
代码还是很简单的。总结下,原生动画行为可以理解为:
- 请求第一帧动画,记录动画启动时间;
- 用回调函数第一个参数(当前时间),减去启动时间,除以动画时长(dur),得到进度(per);
- 根据per改变动画元素的一些属性,启动下一帧动画;
- 当per等于1时,结束动画。
实际上,这就是JK说的保时丢帧策略,dur是一定的,frameTime并不固定。这些示例实际测试中,在firefox5和最新chrome下fps都在62左右,也就是说frameTime在16ms左右。
剩下的工作就是各种封装了。月影实现的QWrap动画组件已经是封装好直接可用的,在gecko和webkit下都是用原生函数,对于没有原生动画的浏览器,使用setInterval进行统一帧管理。提供了常见的css动画、scroll动画和color动画,支持NodeW链式调用。附上地址:https://github.com/wedteam/qwrap-components/tree/master/animation
最后,放上使用浏览器原生函数动画实现的组件:点击这里
update @ 2012-02-10:firefox11开始提供了mozCancelRequestAnimationFrame。对于firefox11+,参考本文webkit代码,按标准实现即可。firefox11开始,注册动画时必须传处理函数,所以本文firefox实现代码,在firefox11+下无法运行。
QWrap动画组件及本文Demo均已更新~
参考:
Timing control for script-based animations
本文链接:https://mailseason.com/post/request-animation-frame.html,参与评论 »
--EOF--
发表于 2011-06-20 22:26:30,并被添加「Animation、JavaScript」标签。查看本文 Markdown 版本 »
专题「JavaScript 漫谈」的其他文章 »
- 改进 ThinkJS 的异步编程方式 (May 15, 2015)
- BOM 和 JavaScript 中的 trim (Dec 07, 2013)
- AMD 的 CommonJS wrapping (Dec 05, 2013)
- FileSystem API 实现文件下载器 2 (Oct 01, 2013)
- 用 FileSystem API 实现文件下载器 (Oct 01, 2013)
- ES6 中的 Set、Map 和 WeakMap (Sep 23, 2013)
- ES6 中的生成器函数介绍 (Sep 20, 2013)
- 尝试 ES6 中的箭头函数 (Sep 11, 2013)
- 使用 Canvas 绘制背景图 (Aug 18, 2013)
- 异步编程:When.js快速上手 (Jun 23, 2013)
Comments
Waline 评论加载中...