擁有更好性能的requesAnimationFrame(Better Performance with requestAnimationFrame)

介紹:javascript

  這篇文章討論的是你能夠(也應該)學習經過使用requestAnimationFrame API,而不是使用以前的setInterval/setTimeout方法,來提升動畫的性能;如何使用requestAnimationFrame。固然,咱們將會爲你展現完善的代碼example of requestAnimationFrame in action.html

  requestAnimationFrame 已經被如今因此的主流瀏覽器支持了,儘管有一些瀏覽器須要加前綴。Erik Moller已經寫了一個能夠在全部瀏覽器均支持requestAnimationFrame的polyfill。 咱們等會會更細緻的討論的。讓咱們從頭開始講吧。。。java

 不太好的老方法

  爲了能展現出requestAnimationFrame是多麼好,咱們首先必須先看看咱們用來作動畫的不太好的老方法。我敢確定我不須要告訴你早在Mozilla在實行第一次mozRequestAnimationFrame實驗時,你可能已經用setTimeout和setInterval建立動畫。我假定你已經熟悉這兩個方法了,因此我就不深刻講解了, 若是你想深刻了解的話,這裏有篇John Resig深度講解的文章:how JavaScript timers workweb

  我不想對這些老方法不屑一顧,不幸的是他們確實有一些缺點。 首先,在切換到一個不一樣的標籤時,甚至當相應頁面最小化時,JavaScript計時器仍將繼續工做。形成的後果就是,瀏覽器繼續運行無形的動畫,這致使了動畫過分繪製,浪費 CPU 週期以及消耗額外的電能等問題。 在移動設備上,這會是特別糟糕的問題。瀏覽器

  第二,計時器不只會繼續運行看不見的動畫,並且當時間到了的時候他們老是還要排隊等待他們的回調函數。讓我來解釋下爲何這種狀況有時會帶來問題 -- 就是說你沒有很好的完成你的工做,由於某些緣由回調函數完成所須要的時間比你設置計時器的時間要長。一旦計時器的時間到了,他們又將排隊到另一個回調函數。儘管前一個尚未完成運行。隨着時間的推移,這個過程一直重複着,你能夠迅速排隊到幾乎無數的計時器, 這將致使瀏覽器中止運行。 圖片1具體說明了這個問題。網絡

Figure 1: If your callback functions take longer than your timers, enqueuing of multiple callback functions can choke up the browser

圖片1:若是回調函數執行的時間比定時器還要長,大量排隊的回調函數將會阻塞瀏覽器dom

  可是即便你的回調函數花費的時間不超過計時器,在這種狀況下,setTimeout和setInterval仍然不是最佳的選擇。他們都只能以固定的速率從新繪製動畫,所以爲了確保動畫平滑,咱們每每寧肯謹慎來選擇一個比顯示刷新略高的頻率。然而,因爲一些幀在顯示刷新率準備繪製動畫以前被繪製了,所以就被丟棄,這就致使了過分繪製的問題。圖片2具體說明了這個問題.函數

Figure 2: Skipped frames can lead to higher CPU usage and battery consumption, and sometimes even choppy animations

圖片2:跳幀能夠致使更高的CPU使用率和電池消耗,甚至有時不穩定的動畫。性能

  當這些方法用於實現循環動畫,這些缺點更加危險。在這樣的場景中,例如在遊戲中或者像個人潮人狗的瘋狂的實驗中,循環動畫致使無休止的的排隊等待新的回調函數。若是你想更多的瞭解網絡循環動畫的歷史。當使用setTimeout和setInterval時,循環動畫的行爲是什麼,以及requestAnimationFrame是怎樣改變咱們的代碼的。我推薦你閱讀Nicholas Zakas寫的 Better JavaScript animations with requestAnimationFrame,他在這面討論的很深刻。學習

 介紹requestAnimationFrame

  requestAnimationFrame正是你所指望的一款API:它將調度動畫繪製的職責直接傳遞給瀏覽器。瀏覽器能夠作的更好,由於,它知道瀏覽器的機制是什麼!

  requestAnimationFrame是W3CTiming control for script-based animations API的一部分。

  requestAnimationFrame是作什麼的

  瀏覽器很是瞭解例如tab和窗口狀態,網頁的哪部分是可見的或者不可見,瀏覽器什麼時候準備繪製,以及哪些其餘動畫也在運行還有哪些動畫是可見的。咱們以前討論過經過讓瀏覽器控制動畫,容許瀏覽器使用信息來優化動畫調度,requestAnimationFrame使用JavaScript定時器追蹤問題。所以,requestAnimationFrame的工做流程以下:

  •  首先,RAF(Request Animation Frame)只繪製哪些對用戶可見的動畫。這意味着RAF會暫停那些隱藏的tabs、最小化的窗口或者隱藏的部分頁面的動畫,直到這些窗口或頁面可見,這樣就不會浪費CPU和電池的壽命。
  • 其次,幀只會在當瀏覽器準備繪製而且沒有其餘的幀等待被繪製時纔會繪製。這意味着這些狀況是不可能發生的:使用requestAnimationFrame繪製動畫須要排隊超過一個回調函數或者使瀏覽器癱瘓。
  • 第三,幀只會在當瀏覽器準備繪製而且沒有其餘的幀等待被繪製時纔會繪製,沒有沒必要要的幀繪製。因此動畫更流暢了,CPU和電池的使用進一步獲得優化。

  我剛剛只是說沒有額外的回調函數隊列,直到當前動畫完成繪製而且已經渲染了。然而,若是每次回調不止一次的調用requestAnimationFrame()將有一個回調隊列,因此將會有額外的回調函數。

  同時,瀏覽器在一個迴流和重繪週期能夠有幾個動畫發生在同一個頁面。

 requestAnimationFrame不作什麼

  requestAnimationFrame不作:

  •  設置連續的動畫; RAF只會安排一個更新,這個更新經過一個id number識別,返回特定的請求。若是須要後續動畫幀,requestAnimationFrame將在回調函數再次被調用。若是須要中止動畫,你可使用cancelAnimationFrame(id).
  • 確保當RAF 繪製時,只有在須要時纔會繪製。
  • 確保動畫的同步性。例如,你同時開始兩個動畫,可是其中一個動畫在可見區域,另外一個不在,可見的動畫會一直繪製而另外一個則不會;當不可見的再次可見時,他們有可能不一樣步了。若是你須要確保他們的同步性,當寫代碼時,就須要注意了。能夠經過確保全部須要同步的動畫的狀態是由一個不受可見性影響的參數決定的(例如,像動畫組的起始時間。)這與依據每一個動畫的前一個幀相反。
  • 繪製直到回調函數執行完成。即便你試圖觸發一個迴流中間回調,在正常狀況下,經過任何可以觸發一個迴流方法、重繪。例如,像getComputedStyle().

 怎樣使用requestAnimationFrame

  RAF如今被全部現代瀏覽器所支持,可是有些瀏覽器須要加前綴。截止寫這篇文章爲止,加前綴或不加前綴的狀況以下:

  •  Opera: 不加前綴 opera15+
  • Chrome:版本24以後不加前綴
  • Safari:加前綴
  • Firefox: 加前綴,儘管版本23以後不加前綴
  • IE:IE10以後不加前綴

  然而,爲了使你的代碼在全部環節下都能兼容,你應該使用Erik Moller’s polyfill, 它提供了強大的跨瀏覽器支持,在Paul Irish’s fantastic original groundwork on the subject的基礎上進行優化的。

  下面就是咱們青蛙動畫演示的簡單代碼:

var requestId = 0;
var animationStartTime = 0;

function animate(time) {
    var frog = document.getElementById("animated");
    frog.style.left = (50 + (time - animationStartTime)/10 % 300) + "px";
    frog.style.top = (185 - 10 * ((time - animationStartTime)/100 % 10) + ((time - animationStartTime)/100 % 10) * ((time - animationStartTime)/100 % 10) ) + "px";
    var t = (time - animationStartTime)/10 % 100;
    frog.style.backgroundPosition = - Math.floor(t / (100/2)) * 60+ "px";
    requestId = window.requestAnimationFrame(animate);
}
function start() {
    animationStartTime = window.performance.now();
    requestId = window.requestAnimationFrame(animate);
}
function stop() {
    if (requestId)
    window.cancelAnimationFrame(requestId);
    requestId = 0;
}

   requestAnimationFrame是一種方法,它會向瀏覽器發出信號代表一個基於腳本的動畫須要經過將回調排入到動畫幀請求回調列表隊列中來從新取樣。它擁有一張關於回調函數id的列表,這些回調函數正在等待執行。調用requestAnimationFrame返回id(requestId),id標識已經插入隊列的回調。這個id能夠以後用於取消回調,使用cancelAnimationFrame(id)來取消。

  requestAnimationFrame方法將參數昨晚回調,回調函數須要來執行去繪製一個新的動畫幀(動畫).回調,反過來,自己就是一個函數,接受動畫更新時的時間戳做爲參數(時間)。

  時間戳是調用優化性能的now方法的結果。你須要肯定任何其餘你想要比較的時間測量也是DOMHighResTimeStamp -- 在上面的例子中,咱們調用window.performance.now和用animationStartTime變量存儲結果。以後在動畫函數(animate)中咱們比較每一幀的開始時間和當前時間來計算青蛙在每種狀況下的位置。

  當編寫動畫時,萬一你遇到舊教程,注意animationStartTime屬性已經建立了,可是如今已經棄用了,因此你本身必須跟蹤開始時間像我上面所示。

  能夠訪問個人requestAnimationFrame demo

 總結

  在這篇文章中咱們討論了requestAnimationFrame是怎樣提升JavaScript動畫性能的,你怎樣使用來兼容全部瀏覽器。我但願這篇文章能激勵你嘗試更多更酷的動畫效果--你能夠更新任何用老方法實現的就動畫代碼。

練習:滿屏的彩虹球

外文翻譯連接:Better Performance With requestAnimationFrame

相關文章
相關標籤/搜索