首頁  >  文章  >  web前端  >  緩動函數requestAnimationFrame 更好的實作瀏覽器經動畫_基礎知識

緩動函數requestAnimationFrame 更好的實作瀏覽器經動畫_基礎知識

WBOY
WBOY原創
2016-05-16 17:47:22970瀏覽

寫緩動函數用到requestAnimationFrame函數,之前了解過一些,但總覺得又不是很了解,所以翻譯一篇老外的文章,以便學習分享。

requestAnimationFrame是什麼?
以前我們做動畫需要一個計時器,每間隔多少毫秒就做出一些改變。現在有個好消息:瀏覽器廠商已經決定提供一個專門做動畫的方法,即requestAnimationFrame(),而且基於瀏覽器的層面也能更好的進行優化。但是呢,這只是一個做動畫的基礎API,即不基於DOM元素的style變化,也不基於canvas,或者WebGL。所以,具體的動畫細節要我們自己寫。

我們為什麼要用它?
對於同時進行的n個動畫,瀏覽器能夠進行最佳化,把原本需要N次reflow和repaint優化成1次,這樣就實現了高品質的動畫。舉個例子,現在有基於JS的動畫,還有基於CSS的transitions,或者SVG SMIL. Plus,如果瀏覽器的某個tab正在運行這樣一個動畫,然後你切到另一個tab,或者乾脆最小化,總之就是你看不見它了,這時瀏覽器就會停止動畫。這將意味著更少的CPU,GPU和更少的記憶體消耗,這樣電池的使用壽命就大大延長了。

如何使用它?

複製程式碼 程式碼如下:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
windowoRequestAnimation. |
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



複製程式碼 程式碼如下:
程式碼如下:


win看到* time */ time){
// time ~= new Date // the unix time
}, /* optional bounding elem */ elem);


先給Chrome且Firefox的版本 程式碼如下:


windowmoz. ; // Firefox
window.webkitRequestAnimationFrame(callback[, element]); // Chrome


參數:
callback:(FF可選,Chrome必選)
 下下 下次repaint調用的函數,函數的第一個參數是當前時間
element:(FF無)
  意譯一下吧:其實就是畫布了,而那個'畫',是動畫。 (the element that visually bounds the entire animation)。對canvas和WebGL來說,它就是元素,對於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 ||. .webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback) { setTimeout(callback, 1000 / 60); }
🎜>🎜>🎜>
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn