在最近一個專案中,因為初期沒有做太好的規劃與人員技術能力有限,在效能方面有很多問題,而我加入這個專案的主要任務就是進行各種效能優化。其中對於重排重繪以及硬體加速相關優化進行的比較多,這種優化方式成本比較低,風險小,在配置較差裝置效果明顯。此文章來之原文連結
近些年,我們總是聽到硬體加速,以及它如何幫助我們提升網頁的動畫效能,讓網頁動畫變得更好,在移動端更流暢。但是我想一大部分經驗少的工程師是不知道硬體加速是如何工作的以及我們如何使用它來幫助我們讓動畫變得更流暢。
硬體加速聽起來非常複雜,像高等數學。在這篇文章中,我會簡明的講解如何在你的前端工程中使用這項技術。
為什麼需要它?
讓我們看一個簡單的動畫例子,一些球疊加在一起。然後移動這一組球按照一個四邊形軌跡移動。最簡單的辦法就是通過設定left
和top
來實現。我們可以通過JavaScript
來實現,但是我們會使用css
來實現。請注意,我沒有使用任何輔助庫,比如Autoprefixer
,但是建議你在專案中使用這種庫來自動補充字首
.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;
}
}
複製程式碼
這是一個線上例子。通過按鈕來執行JavaScript
啟動動畫:
CodePen Preview for Animating overlapping balls with top/left Properties
點選“Start Animation”,你會發現動畫在任何桌面瀏覽器執行的都並不是很順暢。如果你在移動端執行這個動畫網頁,你會看到很嚴重的丟幀現象(譯者注:其實)。為了解決這個問題,我們可以使用transform
的translate()
函式代替對left
和top
的改變。
.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);
}
}
複製程式碼
在下面例子中嘗試執行上面的程式碼:
Animating overlapping balls with CSS transforms
現在動畫比以前流暢多了。非常好!那麼,為什麼會這樣呢?哈,css的transform
並沒有不像操作left
和top
屬性那樣導致重繪。讓我們看看Chrome
中的DevTools
裡面Timeline
頁面的執行結果(譯者注:在Chrome
新版本中,該工具變成了performance
)。
在left
和top
這個例子中,我們可以看到在每一個步驟都有綠色柱狀圖。這是一個效能代價很高的操作。動畫會產生丟幀,這也是我們優化動畫效果的標準。
專案看看css
的transforms
的時間線:
就像你看到的那樣,幾乎沒有綠色的柱形圖出現。
另一個用於跟蹤重繪處理的工具是Chrome
的DevTools
中rendering
裡面的Enable paint flashing
選項。當該選項被選中,綠色的框會出現在重繪的區域。在left
和top
的例子中,當動畫執行的時候,球就有一個綠色的框,因此球就發生了重繪。
在另一個例子中,重繪僅僅發生在動畫開始和結束的時候。
那麼transform
是如何讓動畫不會導致重繪的呢?最直接的答案就是transform
會直接使用硬體加速,在GPU
中執行,繞開了軟體渲染。
硬體加速如何工作的
當瀏覽器接收到頁面的資訊,他會將頁面解釋成DOM
輸。DOM
樹和CSS
讓瀏覽器構建渲染樹。渲染書包含渲染物件 - 在頁面中需要渲染的元素。每一個渲染物件被分配到一個圖層中。每一個圖層被更新到GPU
。這裡的祕訣就在於通過transform
的層會使用GPU
渲染,因此不需要重繪,就像3D圖形一樣。這個轉換是單獨處理的。
在我們的例子中,CSS
的transform
在GPU
直接建立一個新的層。Chrome
的DevTools
的“Show layer borders”選項可以幫助我們檢視那些是單獨的層,開啟這個選項以後單獨的層會具有一個橙色的邊框。
使用transform
樣式的球會被一個橙色的邊框所包圍,因此它在一個獨立的層中:
在此,你可能會問:什麼時候瀏覽器會建立這種獨立的層呢?
在以下情況會產生新的層:
- 3D 或者
CSS
的transform
屬性 <video>
和<canvas>
元素CSS
的filter
屬性- 覆蓋在其它元素之上的元素,比如通過
z-index
提升層級
你可能會想,‘等等,這個例子用的是2D轉換,並不是3D轉換’。是的。這就是為什麼在開始和結束的時候會有兩次重繪產生。
3D轉換和2D轉換的不同在於是否提前生成新的層,如果是2D的話是在實行的時候。在動畫開始的時候,一個新的層被建立,並且被傳入GPU
處理。當動畫結束,獨立的層被移除,結果被重新繪製。
在GPU
渲染元素
並不是所有的CSS
屬性變化都會直接在GPU
處理。只有下面的屬性會這樣處理:
- transform
- opacity
- filter
因此為了頁面更加流暢,高效能的動畫,我們需要儘可能的使用GPU
來處理。
強制在GPU
渲染
在某些情況下,它會在動畫開始的時候嘗試在GPU
渲染一個元素。這可以幫助我們避免建立新層的時候導致重繪。因此,我們需要使用transform hack
技術
.example1 {
transform: translateZ(0);
}
.example2 {
transform: rotateZ(360deg);
}
複製程式碼
這麼做會讓瀏覽器知道,我們希望採用3D的方式做轉換,這會讓瀏覽器在最開始的時候就使用GPU
處理,啟動硬體加速。
這個技術也可以用於結構複雜的元素上。讓我們回到第一個例子,修改這個例子為包含一個球,還有使用filter
屬性並一個具有一個背景圖片的容器。球通過left
和top
實現動畫效果。
Animating a ball with top/left properties
再一次,動畫開始丟幀。因為每一次重繪都導致了大量的效能消耗。
現在讓我們加上transform hack
。
Animating left/top properties with hardware acceleration
現在就沒之前那麼糟糕了。為什麼?因為現在背景再一個獨立的層中處理,因此重繪的代價變得很低。
使用硬體加速需要注意的地方
天下沒有免費的午餐。對於硬體加速,目前有幾個問題。
Memory
大部分重要的問題都是關於記憶體。GPU
處理過多的內容會導致記憶體問題。這在移動端和移動端瀏覽器會導致崩潰。因此,通常不會對所有的元素使用硬體加速。
Font rendering
在GPU
渲染字型會導致抗鋸齒無效。這是因為GPU
和CPU
的演算法不同。因此如果你不在動畫結束的時候關閉硬體加速,會產生字型模糊。
The Near Future
有必要使用transform hack
的地方是提高效能。瀏覽器自身也提供了優化的功能,這也就是will-change
屬性。這個功能允許你告訴瀏覽器這個屬性會發生變化,因此瀏覽器會在開始之前對其進行優化。這裡有一個例子:
.example {
will-change: transform;
}
複製程式碼
遺憾的是,並不是所有瀏覽器都支援這個功能。
文末
概述以下我們都講了什麼:
GPU
渲染可以提高動畫效能GPU
渲染會提高動畫的渲染幀數- 使用會導致
GPU
渲染的CSS
屬性 - 理解如何通過“transform hack”強制讓一個元素在
GPU
渲染