CSS動畫 vs JS動畫:誰更快?

重要申明:感謝翻譯者——MZhou,想看翻譯原文請戳這裏!我只是個轉載者!轉載過程中有些翻譯,我在看了英文原文之後,用了在我看來更爲舒服的表達,望請勿怪!

這篇文章翻譯自 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);

// RAF: 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 還是很流行的(指的是2014年2月2號)
  • 因爲畢竟 transition 不能被 Javascript 原生的控制(他們只不過可以被 Javascript 觸發),因爲瀏覽器不知道如何同時讓 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 那些非常棒的演講:

發佈了45 篇原創文章 · 獲贊 57 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章