1. 程式人生 > >實現動畫之CSS與JavaScript對比

實現動畫之CSS與JavaScript對比

運行時 理解 controls 進行 中間 PE osi 聰明人 為什麽

曾經某個時期,大多數開發者使用 jQuery 給瀏覽器中的元素添加動畫。讓這個淡化,讓那個擴大,很簡單。隨著互動的項目越來越復雜,移動設備的大量增加,表現性能變得越來越重要。Flash 被拋棄,有天賦的動畫開發者使用 HTML5 去實現過去從未實現的效果。他們需要更好的工具去開發復雜的動畫序列並獲得最好的性能。jQuery 並不能夠做到。瀏覽器日漸成熟的同時也開始提供了一些解決方案。

最被廣泛接受的方案是使用 CSS 動畫(以及 Transitions)。幾年中,它成為了業內的熱門話題,在各種研討會上,“硬件加速”和“移動端友好”之類的說法總是不絕於耳。基於 JavaScript 的動畫總是被當做過時的甚至是“骯臟的”。但是真的是這樣嗎?

作為一個對動畫和表現深深著迷的人,我如饑似渴地投入了 CSS 的懷抱,但當我開始發現了一些大問題後,我卻沒有深入研究進去。我被震驚了。

這篇文章用於揭示基於 CSS 的動畫的一些重大缺陷,這樣你可以避免碰到曾經困擾我的問題,同時也教會大家決定何時用 JS 動畫以及何時用 CSS 動畫。

缺少獨立的 scale/rotation/position 控制

對元素的尺寸,旋轉以及位置設置動畫是非常常見的。在 CSS 裏,這些設置都被塞進了transform屬性當中,這樣就不能夠真正地獨立控制它們。例如,你該如何用不同的時間和緩動函數去分別控制元素的rotationscale屬性?可能這個元素會不停的震蕩,並且你想去旋轉它。這只有用 JavaScript 才能實現。

See the Pen Independent Transforms by GreenSock (@GreenSock) on CodePen.

在我看來,這是 CSS 動畫一個很顯著的問題,但是如果你只需要開發一些簡單的動畫效果,它們同時觸發整個 Transform 狀態的話,這也不是什麽大問題。

表現性能

大多數比較都會拿 CSS 動畫和 jQuery 相比,因為 jQuery 的使用非常普遍(就好像 JavaScript 和 jQuery 是同義詞一樣)。但是 jQuery 的動畫性能很差也是眾所周知的。較新的 GSAP 同樣是基於 JavaScript 的,但是毫不誇張的說它的性能相比於 jQuery 提升了近 20 倍。因此,JavaScript 動畫聲名狼藉的部分原因我認為是 jQuery。

使用 CSS 動畫的原因中,常常被提起的是“硬件加速”這個概念。聽起來很高大上是吧?我們來把它分解成兩個部分:

GPU 的使用

GPU 在執行類似控制像素點的移動和應用變換矩陣和透明等方面都做了優化,因此現代瀏覽器會試著把這方面的任務從 CPU 轉交給 GPU 來完成。秘訣在於將應用動畫的元素獨立出來,建立一個自己的 GPU 層,因為只要一個層被創建,讓 GPU 去移動那些像素點並把它們組合起來是很輕松的一件事。不同於以每秒 60 次的速度計算每個像素點的位置,GPU 可以把大量的像素以層的方式儲存,然後我們就可以通過像“把那塊像素向上移動 10 像素再向下移動 5 像素”這樣的方式對像素進行操作了。

註解:GPU 是有圖像存儲空間限制的,因此將每個元素都轉換為一個層是不合適的。一旦 GPU 的存儲空間用完了,速度就會急劇降低。

通過 CSS 聲明動畫能夠讓瀏覽器決定哪個元素應該獲得 GPU 層,並根據實際情況分配資源。很方便。

**但是你知道你可以用 JavaScript 做到同樣的事情嗎?**用一個 3D 特性的觸發器(比如translate3d()或者matrix3d())來讓瀏覽器為這個元素開辟一個 GPU 層。所以 GPU 加速不僅僅是為 CSS 動畫準備的,JavaScript 動畫一樣可以受益!

另外記住,不是所有的 CSS 屬性在 CSS 動畫中都能夠獲得 GPU 的加速。實際上,大多數是不能的。變換(比如 scale,rotation, translation 和 skew)和透明效果是直接受益的。所以不要想當然認為你只要用了 CSS 動畫,所有的效果都得到了 GPU 的幫助,那是不對的。

將計算轉移給不同的線程

“硬件加速”的另一個方面是使用 CPU 的不同線程來進行和動畫相關的計算。再一次的,理論上聽起來很美但是它實際上和性能的開銷無關,開發者往往會高估它帶來的好處。

首先,只有與文檔流無關的屬性才能真正被移交給另一個線程。所以再一次的,變換和透明度是首要受益者。而轉移線程的過程中也是有開銷的。在大多數動畫中,圖像的渲染和文檔的展現已經耗費了大多數的處理器資源(這還沒有算上中間動畫屬性本身所耗費的資源),因此轉移線程所帶來的好處已經微乎其微了。比如,在一個動畫中,98%的資源都用來計算圖像的渲染和文檔流的展現,只有2%的資源用來計算位置、旋轉、透明度等屬性,即使你將這部分的計算速度加快了十倍,整體帶來的速度提升可能也只有1%而已。

性能比較

下面的壓力測試創建了一定數量的圖像元素(點),並且使用動畫讓它們從中點沿著隨機方向以隨機的延遲向邊緣飛去,展現了一種飛舞的粒子效果。將粒子的數量提高,觀察 jQuery,GSAP 和 Zepto 的比較效果。由於 Zepto 使用了 CSS 來展現所有的動畫效果,它的性能應該最好是嗎?

See the Pen Speed Test: GSAP vs CSS Transitions (Zepto) vs jQuery by GreenSock (@GreenSock) on CodePen.

結果證實了大多數網上的結論,CSS 動畫明顯比 jQuery 要快。但是,在我測試的大多數設備和瀏覽器上,GSAP 的性能甚至比 CSS 還要好(在某些情況下差距還很大,比如在 Microsoft Surface RT 上 GSAP 的速度比 Zepto 創建的 CSS 動畫快上 5 倍,並且在 iPad 3 iOS 7 上 GSAP 的動畫變換也比 CSS Transition要快)。

Animated propertiesBetter w/JavaScriptBetter w/CSS
top, left, width, height Windows Surface RT, iPhone 5s (iOS7), iPad 3 (iOS 6), iPad 3 (iOS7), Samsung Galaxy Tab 2, Chrome, Firefox, Safari, Opera, Kindle Fire HD, IE11 (none)
transforms (translate/scale) Windows Surface RT, iPhone 5s (iOS7), iPad 3 (iOS7), Samsung Galaxy Tab 2, Firefox, Opera, IE11 iPad 3 (iOS6), Safari, Chrome

到底有多快?在測試的最初版本中,有每秒渲染幀數這一量化指標,但是很快就發現並沒有一個準確的方式跨瀏覽器去測量 FPS 的值,尤其是 CSS 動畫。並且特定的瀏覽器會得到令人誤解的數字,所以我把它移除了。你可以進行間接的測量,比如逐漸提高粒子數量,切換不同的動畫引擎,觀察動畫的渲染質量(移動平滑穩定,粒子分散度高等等)。畢竟,我們的目標是讓動畫看起來好看。

一些有趣的事情:

  • 當應用動畫的屬性是會影響文檔流的屬性,比如 top/left/width/height 值時,JavaScript 的性能會更好(是 GSAP,不是 jQuery)。
  • 某些設備看起來對 Transiforms 變換做了優化,而另一些處理 top/left/width/height 的變化顯得更好。尤其顯著的是,老的 iOS6 在處理 CSS 動畫變換時表現更好,而更新的 iOS7 系統在這方面退步了,而現在更是明顯慢了。
  • 在 CSS 動畫剛剛啟動的時候都會有一個明顯的延遲,這是由於瀏覽器要計算層並把數據傳輸至 GPU,基於 JavaScript 的動畫同樣存在這樣的問題。所以說“GPU加速”也是有自己的開銷的。(譯者註:這個問題可由新的 CSS 屬性will-change來解決,相關文檔)
  • 在壓力比較大的時候,CSS 變換創建的粒子會以一種類似帶狀或環狀的形式噴出(這似乎是同步時序的問題,是由不同線程處理而造成的)。
  • 在一些瀏覽器中(比如 Chrome),當動畫中的粒子數非常多的時候,文字的透明漸隱就會完全消失,但這樣的情況只存在於 CSS 動畫中!

盡管經過優化的 JavaScript 動畫經常和 CSS 動畫一樣快,甚至更快,但是 CSS 處理 3D 變換的速度還是更快,但這還跟當今瀏覽器處理 16 元素的矩陣的方式有關(強制將數字轉換為連接的字符串,再轉換為數字)。好在這種情況將會改變,在大多數實際的項目中,你並不會感覺到這種區別。

我鼓勵你在自己的項目中通過自己的測試去找到性能最高的實現動畫的方式。不要相信 CSS 動畫性能一定高的說法,也不要讓上文的測試影響你自己的應用中的結果。一定要自己測試,測試,再測試。

運行時的控制和事件

一些瀏覽器允許你在 CSS keyframes 動畫中暫停,但最多也就是這樣了。你不可能在動畫中尋找一個特定的時間點,不可能在半路反轉動畫,不可能變換時間尺度,不可能在特定的位置添加回調函數或是將他們綁定在一大堆回放事件上[?]。JavaScript 提供了很棒的控制方法,請看下面的例子:

See the Pen Impossible with CSS: controls by GreenSock (@GreenSock) on CodePen.

現代動畫大多看重交互性,因此從多種起始變量通過動畫變換為結束狀態就顯得尤其有用(例如可能是基於用戶點擊鼠標的位置),或者改變正在運動中的元素,提前聲明式的 CSS 動畫就做不到這一點。

工作流

對用兩個狀態之間的簡單切換(比如翻轉或是展開菜單),CSS 變換是很好用的。對於連續變換狀態的動畫,你需要使用 CSS keyframes 動畫來實現,它迫使你通過百分比的方式來聲明動畫,就像這樣:

@keyframes myAnimation {
  0% {
    opacity: 0;
    transform: translate(0, 0);
  }
  30% {
    opacity: 1;
    transform: translate(0, 0);
  }
  60% {
    transform: translate(100px, 0);
  }
  100% {
    transform: translate(100px, 100px);
  }
}
#box {
   animation: myAnimation 2.75s;
}

但是動畫進行的時候,你難道不覺得通過時間表示比使用百分比更好嗎?就像“用 1 秒鐘降低透明度,再用 0.75 秒向右滑動,再過 1 秒後向下掉落”。如果你用了幾個小時通過百分比的方式實現後,客戶又要求你將中間的步驟用時增加 3 秒呢?你需要重新計算所有的百分比了!

創建動畫的過程中常常需要進行很多的試驗,尤其是針對時間和緩動方式,這是seek()方法能派上用場的地方。想象你創建了一個 60 秒長度的動畫然後需要對最後 5 秒進行處理,為了看到編輯後的效果,你每次都要先等上 55 秒鐘的時間才行。你可以使用這個方法直接跳過前面的時間,最後再移除這個方法,這樣可以節省大量的時間。

現在創建基於 canvas 的對象以及第三方庫對象的動畫的場景越來越多,而 CSS 動畫又只能以 DOM 元素為目標。也就是說即使你花了大量的時間研究 CSS 動畫,在那樣的項目中也不會有用,最後你還是不得不更換工具。

以下是一些其他的 CSS 動畫不能提供的工作流程相關的便利:

  • 相對值。比如“再旋轉 30 度”或是“將元素由動畫開始的地方再向下移動 100 像素”。
  • 嵌套。想象把一個動畫嵌套在另一個同樣可以被嵌套的動畫之中,等等。設想控制主動畫的時候所有的動畫都能同步。這樣的結構能促進生成模塊化的代碼,利於生產和維護。
  • 進程報告。特定的動畫結束了嗎?如果沒有,它現在處於整個過程中的什麽位置?
  • 特定刪除。有時候,只移除一個元素上所有影響其尺寸(或任何你認為的屬性)的動畫同時允許其他的動畫進行是非常有用的。
  • 代碼簡潔。即使你不添加多余帶前綴的屬性,CSS keyframes 動畫的代碼也會非常冗長。任何想用 CSS 實現稍微復雜一點動畫的人都能證實 CSS 代碼最後會變得非常笨重。實際上,實現動畫的 CSS 代碼的體積可能都會超過 JavaScript 庫的體積(在很多動畫中它還能被緩存和重用)。

有限的效果

以下的任何效果你都無法通過 CSS 實現:

  • 沿著曲線運動(比如貝塞爾曲線)
  • 使用有趣的緩動效果,比如有彈性的,或是彈跳,或是粗糙的效果。雖然有cubic-bezier()這個選項,但它只允許兩個控制點,因此功能也十分有限。
  • 在 CSS keyframes 動畫中針對不同的屬性使用不同的緩動效果。緩動會應用到整個 CSS keyframes 上。
  • 基於物理的動作。例如,有沖擊效果的閃爍,或是恢復狀態,就像這個Demo中的一樣。
  • 滾動位置的動畫。
  • 指定方向的旋轉。比如沿最短方向旋轉270度,或順時針和逆時針。
  • 元素屬性的動畫

兼容性

基於 CSS 的動畫在 IE9 及之前版本的瀏覽器中都無效,我們大多都討厭適配老版本的瀏覽器(尤其是 IE),但現實是我們的一些客戶會要求那樣的支持。

對許多瀏覽器來說,前綴是需要的,不過你可以利用預編譯工具來避免手動書寫它們。

結論

CSS 動畫不好嗎,當然不是。實際上,在實現一些簡單的狀態變換(比如翻轉)並不要求對老版本瀏覽器兼容時 CSS 動畫是非常方便的。3D 變換經常能有非常好的表現(iOS7是個例外)。而且對於喜歡把所有動畫實現都放在 CSS 層的開發者來說 CSS 動畫非常有吸引力。但是,基於 JavaScript 的動畫靈活性更高,能更好的實現復雜的動畫和大量的交互。並且和你聽說的不一樣,它能有和 CSS 動畫一樣,甚至更好的性能。

當和jQuery.animate()進行比較時,我能夠理解為什麽 CSS 動畫會那麽受歡迎了。誰不想要 10 倍的性能提升呢。但是很快就不用在 jQuery 和 CSS 中進行選擇了,基於 JavaScript 的工具 GSAP 提供了全新的可能性,也消除了性能的差距。

這篇文章不是要說 GSAP 或是任何其他的庫;而是要說 JavaScript 動畫不應該背上壞名聲。實際上,JavaScript 是搭建健壯的,靈活的動畫系統的唯一選擇。另外,我也想讓大家看到 CSS 動畫中令人失望的部分,這樣,你可以對動畫實現的方式進行更科學的選擇。

Web Animation 標準能解決問題嗎?

W3C 正在制定一份新的有關解決 CSS 動畫和變換缺點的標準,提供更好的控制和更多的功能。從很多方面來說它確實有很大的進步,但它也是有缺陷的(其中的一些方面甚至是不可能的,它們會和現有的合法的 CSS 標準發生沖突,例如,獨立的變換控制組件看起來是不可能 的)。但那是另一個話題了,我們可以看看事情會如何發展。畢竟還是有很多聰明人在為這份標準努力的。

實現動畫之CSS與JavaScript對比