JS动画之requestAnimationFrame

CSS3中提供了animation的特性,用来通过指定关键帧(@kenframe)来实现动画效果。这么做方便高效。但是浏览器的兼容效果则比较捉急,且不能实现高级的缓动函数,更别说暂停、回放、倒放等功能了。所以大部分炫酷的动画还是采用JS动画来完成。

传统的JS动画无非是通过setInterval或是setTimeout定时器函数实现。这在对动画实时性以及流畅性要求不高时没有什么问题。不过当消息队列较拥挤时,定时效果不能得到保障。同时不同浏览器的UI渲染频率各不相同,很可能与用户设置的时间间隔相冲突。如,相当一部分浏览器的显示频率是16.7ms,此时如果我们设置的时延是10ms就会出现丢帧的情况。为了解决这个问题,requestAnimationFrame千呼万唤始出来。

requestAnimationFrame是window对象在HTML5中的新API。它的使用方法与setTimeout类似,不同的是,requestAnimationFrame()方法将告诉浏览器您希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前调用回调函数更新动画。从而,不同的动画有了一个统一的刷新机制,可以提升系统性能,节省了CPU、GPU和电池等(CSS中的will-change也发挥着类似功能)。

那么requestAnimationFrame的兼容性如何呢?

好像还不错。在老版本的浏览器上,也有shim方法来实现同样的效果,借助了setTimeout函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(function () {
"use strict";

var lastTime = 0,
vendors = ['ms', 'moz', 'webkit', 'o'],
x;

for (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) {
var currTime = new Date().getTime(),
timeToCall = Math.max(0, 16 - (currTime - lastTime)),
id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}

if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
window.clearTimeout(id);
};
}
}());

那么requestAnimationFrame怎么用呢。语法像下面这样。

1
2
3
requestID = window.requestAnimationFrame(callback);               // Firefox 23 / IE10 / Chrome / Safari 7 (incl. iOS)
requestID = window.mozRequestAnimationFrame(callback); // Firefox < 23
requestID = window.webkitRequestAnimationFrame(callback); // Older versions Chrome/Webkit

注意它只接受回调函数作为参数,不需要指定延时哦。同样的,相对应的还有一个cancelAnimationFrame(requestID)方法取消重绘。最常见的用法是在一个动画函数里通过requestAnimationFrame循环调用自身。就像下面这样。

1
2
3
4
5
6
7
8
9
10
11
funFall = function() {
var start = 0, during = 100;
var _run = function() {
start++;
var top = Tween.Bounce.easeOut(start, objBall.top, 500 - objBall.top, during);
ball.css("top", top);
shadowWithBall(top); // 投影跟随小球的动
if (start < during) requestAnimationFrame(_run);
};
_run();
};