css動畫和js動畫比較!
曾經某個時期,大多數開發者使用 jQuery 給瀏覽器中的元素新增動畫。讓這個淡化,讓那個擴大,很簡單。隨著互動的專案越來越複雜,移動裝置的大量增加,表現效能變得越來越重要。Flash 被拋棄,有天賦的動畫開發者使用 HTML5 去實現過去從未實現的效果。他們需要更好的工具去開發複雜的動畫序列並獲得最好的效能。jQuery 並不能夠做到。瀏覽器日漸成熟的同時也開始提供了一些解決方案。
最被廣泛接受的方案是使用 CSS 動畫(以及 Transitions)。幾年中,它成為了業內的熱門話題,在各種研討會上,“硬體加速”和“移動端友好”之類的說法總是不絕於耳。基於 JavaScript 的動畫總是被當做過時的甚至是“骯髒的”。但是真的是這樣嗎?
作為一個對動畫和表現深深著迷的人,我如飢似渴地投入了 CSS 的懷抱,但當我開始發現了一些大問題後,我卻沒有深入研究進去。我被震驚了。
這篇文章用於揭示基於 CSS 的動畫的一些重大缺陷,這樣你可以避免碰到曾經困擾我的問題,同時也教會大家決定何時用 JS 動畫以及何時用 CSS 動畫。
缺少獨立的 scale/rotation/position 控制
對元素的尺寸,旋轉以及位置設定動畫是非常常見的。在 CSS 裡,這些設定都被塞進了transform
屬性當中,這樣就不能夠真正地獨立控制它們。例如,你該如何用不同的時間和緩動函式去分別控制元素的rotation
和scale
屬性?可能這個元素會不停的震盪,並且你想去旋轉它。這隻有用
JavaScript 才能實現。
在我看來,這是 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 來展現所有的動畫效果,它的效能應該最好是嗎?
結果證實了大多數網上的結論,CSS 動畫明顯比 jQuery 要快。但是,在我測試的大多數裝置和瀏覽器上,GSAP 的效能甚至比 CSS 還要好(在某些情況下差距還很大,比如在 Microsoft Surface RT 上 GSAP 的速度比 Zepto 建立的 CSS 動畫快上 5 倍,並且在 iPad 3 iOS 7 上 GSAP 的動畫變換也比 CSS Transition要快)。
Animated properties | Better w/JavaScript | Better 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 提供了很棒的控制方法,請看下面的例子:
現代動畫大多看重互動性,因此從多種起始變數通過動畫變換為結束狀態就顯得尤其有用(例如可能是基於使用者點選滑鼠的位置),或者改變正在運動中的元素,提前宣告式的 CSS 動畫就做不到這一點。
工作流
對用兩個狀態之間的簡單切換(比如翻轉或是展開選單),CSS 變換是很好用的。對於連續變換狀態的動畫,你需要使用 CSS keyframes 動畫來實現,它迫使你通過百分比的方式來宣告動畫,就像這樣:
但是動畫進行的時候,你難道不覺得通過時間表示比使用百分比更好嗎?就像“用 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),但現實是我們的一些客戶會要求那樣的支援。
對許多瀏覽器來說,字首是需要的,不過你可以利用預編譯工具來避免手動書寫它們。