CSS vs JS動畫:誰更快?

zencode.in發表於2016-11-06

  這篇文章翻譯自 Julian Shapiro 的 CSS vs. JS Animation: Which is Faster?。Julian Shapiro 也是 Velocity.js 的創造者。這是一個非常高效、簡單易用的JS動畫庫。他在Web動畫方面有很高的造詣。

  Javascript 動畫怎麼可能總是和 CSS transition 一樣快,甚至更快呢?到底是什麼祕密呢?Adobe 和 Google 是怎麼做到讓他們的富媒體移動網站的速度和 native app 媲美的?

  這篇文章會一步步告訴你為什麼基於 Javascript 的 DOM 動畫庫(比如 Velocity.js 和 GSAP)能夠比 jQuery 和基於 CSS 的動畫庫更高效。

 jQuery

  讓我們從基本開始說起: Javascript 和 jQuery 兩者不能混為一談。Javascript 動畫很快,而 jQuery 動畫很慢。為什麼呢?因為儘管 jQuery 異常強大,但是它的設計目標並不是一個高效的動畫引擎:

  • jQuery 不能避免 layout thrashing (有人喜歡將其翻譯為“佈局顛簸”,會導致多餘relayout/reflow),因為它的程式碼不僅僅用於動畫,它還用於很多其他場景。
  • jQuery的記憶體消耗較大,經常會觸發垃圾回收。而垃圾回收觸發時很容易讓動畫卡住
  • jQuery使用了setInterval而不是 reqeustAnimationFrame(RAF),因為 RAF 會在視窗失去焦點時停止觸發,這會導致jQuery的bug。(目前jQuery已經使用了RAF)

  注意 layout thrashing 會導致動畫在開始的時候卡頓,垃圾回收的觸發會導致動畫執行過程中的卡頓,不使用 RAF 則會導致動畫幀率低。

 實現樣例

  為了避免layout thrashing,我們需要批量訪問和更新DOM。

var currentTop,
    currentLeft;

/* 有 layout thrashing. */
currentTop = element.style.top; /* 訪問 */
element.style.top = currentTop + 1; /* 更新 */

currentLeft = element.style.left; /* 訪問 */
element.style.left = currentLeft + 1; /* 更新 */

/* 沒有 layout thrashing. */
currentTop = element.style.top; /* 訪問 */
currentLeft = element.style.left; /* 訪問 */

element.style.top = currentTop + 1; /* 更新 */
element.style.left = currentLeft + 1; /* 更新 */

  在更新操作之後的訪問操作會強制瀏覽器重新計算頁面元素的樣式(因為要將更新的樣式應用上去才能獲取正確的值)。這在一般操作下沒多大的效能損失,但是放在間隔僅僅16ms的動畫中則會導致顯著的效能開銷。只需要稍微改動下操作的順序就可以大大提高動畫的效能。

  類似地,使用 RAF 也不會讓你大量重構程式碼。讓我們來比較下使用 RAF 和使用 setInterval 的區別:

var startingTop = 0;

/* setInterval: Runs every 16ms to achieve 60fps (1000ms/60 ~= 16ms). */
setInterval(function() {
    /* Since this ticks 60 times a second, we divide the top property's increment of 1 unit per 1 second by 60. */
    element.style.top = (startingTop += 1/60);
}, 16);

/* requestAnimationFrame: Attempts to run at 60fps based on whether the browser is in an optimal state. */
function tick () {
    element.style.top = (startingTop += 1/60);
}

window.requestAnimationFrame(tick);

  你只需要稍微修改下程式碼來使用 RAF,就可以讓你的動畫效能有巨大的提高。

 CSS Transition

  CSS transition 的動畫邏輯是由瀏覽器來執行,所以它的效能能夠比 jQuery 動畫好。它的優勢體現在:

  1. 通過優化 DOM 操作,避免記憶體消耗來減少卡頓
  2. 使用與 RAF 類似的機制
  3. 強制使用硬體加速 (通過 GPU 來提高動畫效能)

  然而實際上Javascript也可以使用這些優化。GSAP 已經做這些優化很久了。Velocity.js 是一個新興的動畫引擎,它不僅僅做了這些優化,甚至走的更遠些。我們稍後會談到這些。

  面對事實,讓 Javascript 動畫得以媲美 CSS 動畫的效能只是我們偉大計劃的第一步。第二步才是重頭戲,要讓 Javascript 動畫比 CSS 動畫還要快!

  讓我們來看看 CSS 動畫庫的缺陷吧:

  • Transition 強制使用了 GPU 的硬體加速。導致瀏覽器一直處於高負荷運轉的狀態,這反而會讓動畫變的卡頓。這在移動瀏覽器上更為嚴重。(特別要說明的是,當資料在瀏覽器的主執行緒和合成執行緒之間頻繁傳輸的時候特別消耗效能,故容易導致卡頓。某些 CSS 屬性,不會受到影響。Adobe 的部落格談到過這個問題。
  • IE 10以下的瀏覽器不支援 transition。而目前 IE8 和 IE9 還是很流行的。
  • transition 不能完全被 Javascript 控制(只能通過 Javascript 來觸發 transition),因為瀏覽器不知道如何同時讓 Javascript 控制動畫又同時優化動畫的效能。

  反過來說: 基於 Javascript 可以決定什麼時候啟用硬體加速,它可以支援全版本的 IE,並且它完全可以進行批量動畫的優化。

我的建議是:當你只在移動平臺上開發,並且動畫只是簡單的狀態切換,那麼適合用純 CSS transition。在這種情況下,transition 是高效能的原生支援方案。它可以讓你將動畫邏輯放在樣式檔案裡面,而不會讓你的頁面充斥 Javascript 庫。然而如果你在設計很複雜的富客戶端介面或者在開發一個有著複雜UI狀態的 app。那麼我推薦你使用一個動畫庫,這樣你的動畫可以保持高效,並且你的工作流也更可控。有一個特別的庫做的特別棒,它可以用 Javascript 控制 CSS transition。這就是 Transit

 Javascript 動畫

  所以 Javascript 可以比 CSS transition 效能更好。但是它到底有多塊呢?它快到足夠可以構建一個3D 動畫的demo,通常需要用到 WebGL 才能完成。並且它快到足夠搭建一個多媒體小動畫,通常需要 Flash 或者 After Effects 才能完成。並且它還快到可以構建一個虛擬世界,通常需要 canvas 才能完成。

  為了更直接的來比較主流動畫庫的效能,包括 Transit(使用了 CSS transition),讓我們開啟Velocity的官方文件

  之前那個問題還在:Javascript 是如何達到高效能的呢?下面是一個列表,列舉了基於 Javascript 的動畫庫能做的事情:

  • 同步DOM -> 在整個動畫鏈中微調堆疊以達到最小的layout thrashing。
  • 快取鏈式操作中的屬性值,這樣可以最小化DOM的查詢操作(這就是高效能 DOM 動畫的阿喀琉斯之踵)
  • 在同一個跨同層元素的呼叫中快取單位轉化比率(例如px轉換成%、em等等單位)
  • 忽略那些變動小到根本看不出來的DOM更新

  讓我們重新溫習下之前學到的關於layout thrashing的知識點。Velocity.js 運用了這些最佳實踐,快取了動畫結束時的屬性值,在緊接的下一次動畫開始時使用。這樣可以避免重新查詢動畫的起始屬性值。

$element
    /* Slide the element down into view. */
    .velocity({ opacity: 1, top: "50%" })
    /* After a delay of 1000ms, slide the element out of view. */
    .velocity({ opacity: 0, top: "-50%" }, { delay: 1000 });

  在上面的樣例中,第二次呼叫 Velocity 時已經知道了 opacity 的起始值為 1,top 的值為 50%。

  瀏覽器也可以使用與此類似的優化,但是要做這些事情太過激進,使用場景也會受到限制,開發者就有可能會寫出有bug的動畫程式碼。jQuery就是因為這個原因沒有使用RAF(如上所說),瀏覽器永遠不會強行實施可能打破規範或者可能偏離期望行為的優化。

  最後,讓我們來比較下兩個Javascript框架(velocity.js 和 GSAP)。

  • GASP 是一個快速且功能豐富的動畫平臺。Velocity則更為輕量級,它大大地改善了UI動畫效能和工作流程。
  • GSAP 需要付費才能用於商業產品。Velocity 是完全免費的,它使用了自由度極高的 MIT 協議。
  • 效能方面,兩者幾乎相當,很難區分勝負。

我個人推薦在你需要如下功能時使用 GSAP:精確控制時間(例如 remapping,暫停/繼續/跳過),或者需要動作(例如:貝賽爾曲線路徑),又或者複雜的動畫組合/佇列。這些特性對遊戲開發或者複雜的應用很重要,但是對普通的 web app 的 UI 不太需要。

 Velocity.js

  之前提到了 GSAP 有著豐富的功能,但這不代表 Velocity 的功能簡單。相反的,Velocity 在 zip 壓縮之後只有 7kb,它不僅僅實現了 jQuery animate 方法的所有功能,還包含了 顏色、transforms、loop、easings、class 動畫和滾動動畫等功能。

  簡單的說就是 Velocity 包含了 jQuery、 jQuery UI 和 CSS transition 的功能。

  更進一步從易用性的角度來講,Velocity 使用了 jQuery 的$.queue() 方法,因此可以無縫過渡到 jQuery 的$.animate()、$.fade()和$.delay()方法。並且 Velocity 的語法和$.animate()一摸一樣,所以我們根本不需要修改頁面的現有程式碼。

  讓我們快速過一下 Velocity.js 的例子:

$element
    .delay(1000)
    /* Use Velocity to animate the element's top property over a duration of 2000ms. */
    .velocity({ top: "50%" }, 2000)
    /* Use a standard jQuery method to fade the element out once Velocity is done animating top. */
    .fadeOut(1000);

  如下是一個高階用法:滾動網頁到當前元素並且旋轉元素。這樣的動畫只需要簡單的幾行程式碼:

$element
    /* Scroll the browser to the top of this element over a duration of 1000ms. */
    .velocity("scroll", 1000)
    /* Then rotate the element around its Y axis by 360 degrees. */
    .velocity({ rotateY: "360deg" }, 1000);

 總結

  Velocity 的目標是成為 DOM 動畫領域效能最好易用性最高的庫。這篇文章主要關注了效能方面。易用性方面可以前往 VelocityJS.org 瞭解。

  在結束之前,請記住一個高效能的 UI 絕不僅僅是選擇一個正確的動畫庫。頁面上的其他程式碼也需要優化。可以看看Google那些非常棒的演講:

相關文章