談談requestAnimationFrame的動畫循環

吳青強 9年前發布 | 9K 次閱讀 JavaScript開發 JavaScript

一. 動畫的循環間隔

編寫動畫循環的關鍵,是要知道延遲時間多長合適。一方面,循環時間必須足夠短,這樣才能保證動畫效果更平滑流暢;另一方面,循環還要足夠長,這樣才 能保證瀏覽器有能力渲染產生的變化。大多數顯示器的刷新頻率是60Hz,相當于每秒鐘重繪60次。大多數瀏覽器都會對重繪操作加以限制,不超過顯示器的重 繪頻率,因為即使超過了這個頻率,用戶體驗也不會有提升。

因此最平滑動畫的最佳循環間隔是1000ms/60,約等于17ms。以這個循環間隔重繪的動畫是平滑的,因為這個速度最接近瀏覽器的最高限速。為了適應17ms的循環間隔,多重動畫可能需要加以節制,以便不會完成得太快。

雖然與使用多組setTimeout()相比,使用setInterval()的動畫循環效率更高。但是無論setTimeout()還是setInterval()都 不十分精確。為它們傳入的第二個參數,實際上只是指定了把動畫代碼添加到瀏覽器UI線程隊列以等待執行的時間。如果隊列前面已經加入了其他任務,那動畫代 碼就要等前面的任務執行完成后再執行。如果UI線程繁忙,比如忙于處理用戶操作,那么即使把代碼加入隊列也不會立即執行。

因此,知道什么時候繪制下一幀是保證動畫平滑的關鍵。然而,面對不十分精確的setTimeout()和setInterval(),開發人員至今都沒有辦法確保瀏覽器按時繪制下一幀。以下是幾個瀏覽器的計時器精度:

  • IE8及其以下版本瀏覽器: 15.6ms;
  • IE9及其以上版本瀏覽器:4ms;
  • Firefox和Safari:10ms;
  • Chrome:4ms。

更為復雜的是,瀏覽器開始限制后臺標簽頁或不活動標簽頁的計數器。因此,即使你優化了循環間隔,可能仍然只能接近你想要的效果。

二. requestAnimationFrame()

MozillaRobert O’Callahan 指出,CSS變換動畫的優勢在于瀏覽器知道動畫什么時候開始,因此會計算出正確的循環間隔,在適當的時候刷新UI。而對于JavaScript動畫,瀏覽器就無從知曉什么時候開始。

因此Robert O’Callahan的方案是,創建一個新方法mozRequestAnimationFrame(),通過它告訴瀏覽器某些代碼將要執行動畫。這樣瀏覽器可以在運行某些代碼后進行適當的優化。

setTimeout()setInterval()方法不同,requestAnimationFrame()不需要調用者指定幀速率,瀏覽器會自行決定最佳的幀效率。

requestAnimationFrame()方法接收一個參數,即在重繪屏幕前調用以個函數。這個函數負責改變下一次重繪時的DOM樣式。為了創建動畫循環,可以像使用setTimeout()一樣,把多個對requestAnimationFrame()的調用連綴起來。如:

function drawFrame() {
    window.requestAnimationFrame(drawFrame);
    // animation code...
}
window.requestAnimationFrame(drawFrame);

三. requestAnimationFrame()的兼容性

3.1 requestAnimationFrame()的兼容性封裝:

由于mozRequestAnimationFrame()是HTML5的新功能,目前各大瀏覽器的支持情況各異。具體兼容情況個參考http://caniuse.com/#search=RequestAnimationFrame。如果希望代碼具備更好的跨平臺性,可以考慮使用下面的代碼實現各平臺兼容性:

if(!window.requestAnimationFrame) {
    window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
        var self = this, start, finish;
        return window.setTimeout(function() {
            start = +new Date();
            callback(start);
            finish = +new Date();
            self.timeout = 1000/60 - (finish - start);
        }, self.timeout);
    });
}

這段代碼先檢查了window.requestAnimationFrame函數的定義是否存在。如果不存在,就遍歷已知的各種瀏覽器實現并替代該函數。如果還是找不到一個與瀏覽器相關的實現,它最終會采用基于JavaScript定時器的動畫以每秒60幀的間隔調用setTimeout函數。

mozRequestAnimationFrame()會接收一個時間碼(從1970年1月1日起至今的毫秒數),表示下一次重繪的實際發生時間。這樣,mozRequestAnimationFrame()就會根據這個時間碼設定將來的某個時刻進行重繪。

但是webkitRequestAnimationFrame()msRequestAnimationFrame()不會給回調函數傳遞時間碼,因此無法知道下一次重繪將發生在什么時間。

如果要計算兩次重繪的時間間隔,Firefox中可以使用既有的時間碼,而在Chrome和IE則可以使用不太精確地Date()對象。

3.2 cancelRequestAnimFrame()的兼容性封裝:

W3C也提供了cancelRequestAnimationFrame()方法,用于取消回調函數。requestAnimationFrame()方法會返回一個對象,用做標識回掉函數身份。若要取消回調函數的執行,可將其傳給cancelRequestAnimationFrame()

window.cancelRequestAnimFrame = ( function() {
    return window.cancelAnimationFrame ||
        window.webkitCancelRequestAnimationFrame ||
        window.mozCancelRequestAnimationFrame ||
        window.oCancelRequestAnimationFrame ||
        window.msCancelRequestAnimationFrame ||
        clearTimeout;
} )();

3.3 requestAnimationFrame()升級版封裝方法:

另外還有一種更優雅的requestAnimationFrame()的兼容性封裝方法: (引用自:https://gist.github.com/paulirish/1579671

(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 - (currTime - lastTime));
        var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
          timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    };

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

}()); </code></pre>

來自:http://www.dengzhr.com/js/937

 本文由用戶 吳青強 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!