原文連接: An Introduction to Hardware Acceleration with CSS Animations
過去幾年來,咱們常常會據說「硬件加速」及其對頁面性能帶來的提高——使動畫即便在移動設備上也能有更順滑的表現。不過,我認爲有很多開發者並不真正瞭解「硬件加速」是如何工做的,也不清楚該如何利用它來使咱們的動畫更加出彩。css
「硬件加速」聽起來是涉及高等數學知識的複雜玩意,在本篇文章中,我將會闡述這一特性,並演示如何在前端項目中應用。前端
讓咱們先看一個例子:有多個重疊在一塊兒的球(Z軸重疊,看起來就只有1個球),咱們須要用動畫移動這些球。最簡單的辦法是改變 top
和 left
屬性。固然你也可使用 JavaScript
去作,但這裏我會使用 CSS
(注意:如下例子我沒有使用瀏覽器前綴,你可使用如 Autoprefixer 來保證瀏覽器兼容性):git
.ball-running { animation: run-around 4s infinite; } @keyframes run-around { 0%: { top: 0; left: 0; } 25% { top: 0; left: 200px; } 50% { top: 200px; left: 200px; } 75% { top: 200px; left: 0; } }
點擊查看DEMO(能夠點擊按鈕經過 JavaScript
來啓動動畫)。github
點擊「Start Animation」後,你會注意到即便在桌面瀏覽器上,動畫看起來也不太順滑(譯者注:若是在你的瀏覽器上看起來很順滑,說明你的電腦性能太好了,你能夠打開 F12 -> Performance -> CPU,設置 CPU 到 4x slowdown 後再試)。要是你是在移動設備上測試的話,幀率可能會遠低於 60fps。咱們可使用 CSS transform 的 translate()
方法代替操做 left
和 top
,以解決這個問題:web
.ball-running { animation: run-around 4s infinite; } @keyframes run-around { 0%: { transform: translate(0, 0); } 25% { transform: translate(200px, 0); } 50% { transform: translate(200px, 200px); } 75% { transform: translate(0, 200px); } }
點擊查看DEMOchrome
太棒了,如今動畫變得更加順滑了!但這是爲何呢?這是由於,與 left
和 top
的改變不一樣,CSS transforms 不會致使重繪(repaints)。讓咱們看一下在動畫執行過程當中 Chrome's DevTools 的 Timeline 是什麼樣的:canvas
在改變 left
和 top
的例子中,咱們能夠看見每次動畫前的一塊綠色條,這表明一次開銷昂貴的重繪操做。咱們須要讓動畫幀率在 60fps 以保證流暢,但這裏的幀率顯然不足 60fps。瀏覽器
而後咱們在看一下使用 CSS transforms 以後的 Timeline:ide
能夠看到,動畫過程當中已經沒有綠色條區域了。post
Chrome's DevTools 提供了另外一個「Enable paint flashing」功能能夠追蹤重繪的過程。你能夠打開 Chrome's DevTools,按下 Esc,選擇「Rendering」標籤(譯者注:若是沒有此標籤的話,點擊左側三個點選擇此標籤)。當此功能開啓後,重繪區域會出現綠色方框。在改變 left
和 top
的動畫例子中,整個動畫過程當中小球周圍都有綠色方框,說明一直都在執行重繪。
而在使用 CSS transforms 的例子中,重繪的方框只在動畫的首幀和末幀出現。
那麼 CSS transforms 到底是如何作到動畫過程當中不重繪的呢?簡單來講,CSS transforms 是直接發生在利用硬件加速的顯存中,從而避開了軟件的渲染。咱們一塊兒來看一下詳細的說明。
當瀏覽器收到一個 HTML 後,會進行解析並構建 DOM 樹。隨後,瀏覽器能夠根據 DOM 樹和 CSS 構建出渲染樹,渲染樹是由頁面上須要渲染的元素組成的。每一個渲染元素都會被分配給一個圖形層,每一個圖形層則會被做爲一個紋理(texture)提交給 GPU。而這裏的祕密在於,圖形層有可能會在沒有重繪的狀況下直接在 GPU 中轉變,就好比 3D 圖像。這個轉變是由一個獨立的合成器(Compositor)流程完成的,你能夠閱讀 the composition in Chrome here 瞭解更多。
在咱們的例子中,CSS transform 建立了一個能夠直接被 GPU 轉換的合成層(composite layer),在 Chrome's DevTools 中能夠經過勾選「Show layer borders」選項查看合成層,每一個合成層周圍都會有個橙色的邊框。
咱們在 CSS transforms 動畫裏的球都有橙色邊框,而且都被分到不一樣的合成層中。
在這裏你可能會問:「瀏覽器會在何時建立一個獨立的合成層?」
主要存在如下這些狀況:
<video>
和 <canvas>
元素讀到這裏你是否會想:「等一下!這個例子使用的是 2D 的 transform,不是 3D 的。」。沒錯,這就是爲何在咱們的 timeline 中,在動畫的首位會有兩次額外的重繪操做。
2D 和 3D transform 的區別在於,瀏覽器對 3D transform 會事先建立一個獨立的合成層,而對 2D transform 則是實時地建立合成層。在動畫開始的時候,一個新的合成層被建立而且向 GPU 載入紋理,這一操做致使了一次重繪。在隨後的過程當中,動畫由 GPU 中的合成器完成。當動畫結束後,先前被建立的合成層會被移除,這一操做會再致使一次重繪。
並非元素上全部的 CSS 屬性變化均可以在 GPU 裏處理的,只有如下屬性能夠被支持:
所以,爲了獲得流暢、高質量的動畫效果,咱們應該儘量使用這些 GPU 友好的 CSS 屬性。
在某些狀況下,咱們可能須要在動畫開始前就讓元素在 GPU 中渲染,這麼作能夠避免動畫開始時建立新層的那一次重繪。咱們能夠用一個叫「transform hacky」的小技巧來作到這些:
.example1 { transform: translateZ(0); } .example2 { transform: rotateZ(360deg); }
這會讓瀏覽器知道咱們要進行 3D 變化,瀏覽器會所以建立一個新層並事先將元素移到 GPU 中,以此來觸發了硬件加速。
若是由於背後其餘元素等致使重繪耗費高昂的狀況下,咱們也可使用這個技巧。咱們回到第一個例子,作一些小的調整:如今咱們有一個球以及一個容器,容器的背景圖片使用了 CSS filters 進行模糊處理。如今咱們讓這個小球使用 left
和 top
進行動畫:
咱們發現小球的運動有些卡頓,這是由於每次模糊背景的重繪耗費都十分高昂。
如今咱們在容器上使用 「transform hacky」試試:
效果不錯!咱們的動畫變得順滑多了。但這是爲何呢?由於咱們如今已經把繪製代價高昂的背景移到了另外一個合成層上,而單獨繪製小球的性能消耗就小得多了!
天下可沒有免費的午飯,硬件加速也同時會帶來如下幾點問題:
內存是最嚴重的問題,要知道在GPU里加載太多的紋理(texture)會致使嚴重的內存負擔。這在移動設備上尤爲明顯,甚至能夠直接引發瀏覽器崩潰!因此要注意使用硬件加速可能帶來內存負擔的後果,千萬不要在頁面上每個元素都使用硬件加速。
因爲 GPU 與 CPU 之間不一樣的渲染機制,在 GPU 中的渲染會影響字體的抗鋸齒。你能夠在動畫結束後關閉硬件加速,但在動畫期間字體的渲染仍是會模糊。關於問題渲染問題的更多解釋你能夠閱讀 Keith Clark 的這篇文章。
使用「transform hacky」這種技巧來建立分離的合成層,這種方法看上去仍是太繁瑣了。瀏覽器爲了提供一個更直接的方法,引入了 will-change
屬性。這個新特性容許你事先告訴瀏覽器哪一個屬性要發生改變,瀏覽器即可以對此做出相應的優化。這裏給一個 transform
屬性即將發生改變的例子:
.example { will-change: transform; }
不過並非全部瀏覽器都支持 will-change
屬性,你也能夠經過如下參考資料瞭解更多 will-change
屬性相關的知識:
總結一下咱們提到的內容:
譯者注:第一次翻譯,不周到之處還請指出,歡迎留言討論