【譯】CSS動畫之硬件加速

原文連接: An Introduction to Hardware Acceleration with CSS Animations

過去幾年來,咱們常常會據說「硬件加速」及其對頁面性能帶來的提高——使動畫即便在移動設備上也能有更順滑的表現。不過,我認爲有很多開發者並不真正瞭解「硬件加速」是如何工做的,也不清楚該如何利用它來使咱們的動畫更加出彩。css

「硬件加速」聽起來是涉及高等數學知識的複雜玩意,在本篇文章中,我將會闡述這一特性,並演示如何在前端項目中應用。前端

爲何要理解

讓咱們先看一個例子:有多個重疊在一塊兒的球(Z軸重疊,看起來就只有1個球),咱們須要用動畫移動這些球。最簡單的辦法是改變 topleft 屬性。固然你也可使用 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() 方法代替操做 lefttop,以解決這個問題: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

太棒了,如今動畫變得更加順滑了!但這是爲何呢?這是由於,與 lefttop 的改變不一樣,CSS transforms 不會致使重繪(repaints)。讓咱們看一下在動畫執行過程當中 Chrome's DevTools 的 Timeline 是什麼樣的:canvas

DevTools Timeline during the animation with left and top

在改變 lefttop 的例子中,咱們能夠看見每次動畫前的一塊綠色條,這表明一次開銷昂貴的重繪操做。咱們須要讓動畫幀率在 60fps 以保證流暢,但這裏的幀率顯然不足 60fps。瀏覽器

而後咱們在看一下使用 CSS transforms 以後的 Timeline:ide

DevTools Timeline during the animation with CSS transform

能夠看到,動畫過程當中已經沒有綠色條區域了。post

Chrome's DevTools 提供了另外一個「Enable paint flashing」功能能夠追蹤重繪的過程。你能夠打開 Chrome's DevTools,按下 Esc,選擇「Rendering」標籤(譯者注:若是沒有此標籤的話,點擊左側三個點選擇此標籤)。當此功能開啓後,重繪區域會出現綠色方框。在改變 lefttop 的動畫例子中,整個動畫過程當中小球周圍都有綠色方框,說明一直都在執行重繪。

而在使用 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 動畫裏的球都有橙色邊框,而且都被分到不一樣的合成層中。

Show layer borders during the animation with css transform

在這裏你可能會問:「瀏覽器會在何時建立一個獨立的合成層?」

主要存在如下這些狀況:

  • CSS 的透視或 3D 變形(咱們的例子就是)
  • <video><canvas> 元素
  • 使用 CSS filters 時
  • 當一個元素覆蓋在另外一個有合成層的元素之上時(如:z-index)

讀到這裏你是否會想:「等一下!這個例子使用的是 2D 的 transform,不是 3D 的。」。沒錯,這就是爲何在咱們的 timeline 中,在動畫的首位會有兩次額外的重繪操做。

2D css transformations initiate repainting before and after animation

2D 和 3D transform 的區別在於,瀏覽器對 3D transform 會事先建立一個獨立的合成層,而對 2D transform 則是實時地建立合成層。在動畫開始的時候,一個新的合成層被建立而且向 GPU 載入紋理,這一操做致使了一次重繪。在隨後的過程當中,動畫由 GPU 中的合成器完成。當動畫結束後,先前被建立的合成層會被移除,這一操做會再致使一次重繪。

在 GPU 裏渲染元素

並非元素上全部的 CSS 屬性變化均可以在 GPU 裏處理的,只有如下屬性能夠被支持:

  • transform
  • opacity
  • filter

所以,爲了獲得流暢、高質量的動畫效果,咱們應該儘量使用這些 GPU 友好的 CSS 屬性。

強制元素在 GPU 裏渲染

在某些狀況下,咱們可能須要在動畫開始前就讓元素在 GPU 中渲染,這麼作能夠避免動畫開始時建立新層的那一次重繪。咱們能夠用一個叫「transform hacky」的小技巧來作到這些:

.example1 {
  transform: translateZ(0);
}

.example2 {
  transform: rotateZ(360deg);
}

這會讓瀏覽器知道咱們要進行 3D 變化,瀏覽器會所以建立一個新層並事先將元素移到 GPU 中,以此來觸發了硬件加速。

若是由於背後其餘元素等致使重繪耗費高昂的狀況下,咱們也可使用這個技巧。咱們回到第一個例子,作一些小的調整:如今咱們有一個球以及一個容器,容器的背景圖片使用了 CSS filters 進行模糊處理。如今咱們讓這個小球使用 lefttop 進行動畫:

點擊查看demo

咱們發現小球的運動有些卡頓,這是由於每次模糊背景的重繪耗費都十分高昂。

如今咱們在容器上使用 「transform hacky」試試:

點擊查看demo

效果不錯!咱們的動畫變得順滑多了。但這是爲何呢?由於咱們如今已經把繪製代價高昂的背景移到了另外一個合成層上,而單獨繪製小球的性能消耗就小得多了!

謹慎使用硬件加速

天下可沒有免費的午飯,硬件加速也同時會帶來如下幾點問題:

內存

內存是最嚴重的問題,要知道在GPU里加載太多的紋理(texture)會致使嚴重的內存負擔。這在移動設備上尤爲明顯,甚至能夠直接引發瀏覽器崩潰!因此要注意使用硬件加速可能帶來內存負擔的後果,千萬不要在頁面上每個元素都使用硬件加速。

字體渲染

因爲 GPU 與 CPU 之間不一樣的渲染機制,在 GPU 中的渲染會影響字體的抗鋸齒。你能夠在動畫結束後關閉硬件加速,但在動畫期間字體的渲染仍是會模糊。關於問題渲染問題的更多解釋你能夠閱讀 Keith Clark 的這篇文章

展望將來

使用「transform hacky」這種技巧來建立分離的合成層,這種方法看上去仍是太繁瑣了。瀏覽器爲了提供一個更直接的方法,引入了 will-change 屬性。這個新特性容許你事先告訴瀏覽器哪一個屬性要發生改變,瀏覽器即可以對此做出相應的優化。這裏給一個 transform 屬性即將發生改變的例子:

.example {
  will-change: transform;
}

不過並非全部瀏覽器都支持 will-change 屬性,你也能夠經過如下參考資料瞭解更多 will-change 屬性相關的知識:

總結

總結一下咱們提到的內容:

  • 利用 GPU 能夠提高的動畫質量
  • GPU 渲染的動畫在任何設備上都應是 60fps
  • 使用 GPU 友好的 CSS 屬性
  • 理解如何使用「transform hacky」技巧強制讓一個元素到 GPU 中渲染

譯者注:第一次翻譯,不周到之處還請指出,歡迎留言討論

相關文章
相關標籤/搜索