傳統(tǒng)的JS動畫都是用 setTimeout 和 setInterval 實(shí)現(xiàn)的,后來無意中在網(wǎng)上看到一個(gè)新的JS函數(shù) requestAnimationFrame 用它來替代傳統(tǒng)的JS動畫方法,說是效果更好,當(dāng)時(shí)也沒有仔細(xì)深究。直到昨天去魅族面試的時(shí)候,面試官問我有什么新的辦法可以替代傳統(tǒng)的JS動畫,我說“我知道一個(gè)叫 requestAnimationFrame 的函數(shù),它的執(zhí)行效果更好”。但是讓我仔細(xì)描述的時(shí)候,我就說不下去了,這也是我寫這篇博客的初衷,我們學(xué)習(xí)的過程中一定要知其然比知其所以然,不要什么都略懂,最后落得跟半吊子一樣。
定時(shí)器一直都是JS動畫的核心技術(shù)。而編寫動畫循環(huán)的關(guān)鍵是要知道延遲時(shí)間多長合適。一方面,循環(huán)間隔必須足夠短,這樣才能讓不同的動畫效果顯得平滑流暢;另一方面,循環(huán)間隔還要足夠長,才能確保瀏覽器有能力渲染產(chǎn)生的變化。
大多數(shù)電腦顯示器的刷新頻率是60HZ,也就是每秒鐘重繪60次。大多數(shù)瀏覽器都會對重繪操作加以限制,不超過顯示器的重繪頻率,因?yàn)榧词钩^那個(gè)頻率用戶體驗(yàn)也不會提升。因此,最平滑動畫的最佳循環(huán)間隔是 1000ms / 60 ,約為16.7ms。
傳統(tǒng)的 setTimeout 和 setInterval 它們都不是很精確,因?yàn)樗鼈儗?shí)際上只是把動畫代碼添加到瀏覽器UI線程隊(duì)尾以等待執(zhí)行時(shí)間,如果它們前面有其它任務(wù),則必須等前面的任務(wù)執(zhí)行完在執(zhí)行動畫代碼。
而 requestAnimationFrame 采用系統(tǒng)時(shí)間間隔,讓各種動畫效果能夠有一個(gè)統(tǒng)一的刷新機(jī)制,從而節(jié)省系統(tǒng)資源,提高系統(tǒng)性能,改善視覺效果。它有如下三個(gè)特點(diǎn):
- 會把每一幀中所有的DOM操作集中起來,在一次動畫操作就完成,并且動畫的時(shí)間間隔緊緊跟隨瀏覽器的刷新頻率(不需要設(shè)置時(shí)間間隔)。
- 在隱藏或者不可見的元素中,不會進(jìn)行動畫操作。
- 當(dāng)瀏覽器不是激活狀態(tài),不會進(jìn)行動畫操作。
下面是一個(gè)兼容所有瀏覽器的使用 requestAnimationFrame 的代碼(IE9-無該方法)
(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.7 - (currTime - lastTime));
var id = setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
}
}
}());
最后附上我利用 requestAnimationFrame 制作的一個(gè) 跳動的小球 的DEMO。