提問
被問到怎麼設置一個從左到右的動畫?我回答了設置 key-frame 的動畫名,加上 from 和 to 去設置 left 屬性的變化。
追問設置 left 是最好的選擇嗎?有更快更流暢的方案嗎?我回答了 translateX。
然爲什麼 transform: translateX()
比 left 更流暢?我講不出所以然來,只記得有這麼個結論。
解答
查資料發現原來是設置 left 屬性會頻繁的造成瀏覽器迴流重排,而 transform 和 opacity 屬性不會,因爲它是作爲合成圖層發送到 GPU 上,由顯卡執行的渲染,這樣做的優化如下
- 可以通過亞像素精度得到一個運行在特殊優化過的單位圖形任務上的平滑動畫,並且運行非常快。
- 動畫不再綁定到 CPU 重排,而是通過 GPU 合成圖像。 即使運行一個非常複雜的 JavaScript 任務,動畫仍然會很快運行。
合成圖層
所謂合成就是將頁面的已繪製部分放在一起在屏幕上顯示的過程。
我們舉個例子,若以 left 作爲動畫的屬性值,動畫的過程是這樣的,下面的例子和圖片出自 這樣使用 GPU 渲染 CSS 動畫
<style>
#a, #b {
position: absolute;
}
#a {
left: 10px;
top: 10px;
z-index: 2;
animation: move 1s linear;
}
#b {
left: 50px;
top: 50px;
z-index: 1;
}
@keyframes move {
from { left: 30px; }
to { left: 100px; }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>
那麼以 transform
作爲動畫的話
<style>
#a, #b {
position: absolute;
}
#a {
left: 10px;
top: 10px;
z-index: 2;
}
#b {
left: 50px;
top: 50px;
z-index: 1;
animation: move 1s linear;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(70px); }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>
隱式合成:值得注意的一點是,上文 a 的 z-index 在 b 之上,而 b 卻是在單獨的合成層上,這種情況下瀏覽器會強制爲元素 a 創建一個新的合成圖層,並添加另一個重繪圖,這種方式被成爲隱式合成(請記住這種特殊的 GPU 合成模式並非 css 規範的一部分,知識瀏覽器內部的優化)。
事實上隱式合成非常頻繁,瀏覽器將元素提升爲合成層的原因有很多,包括
- 3D transforms: translate3d, translateZ等等;
<video>
,<canvas>
和<iframe>
元素;- СSS transitions 和 animations 而有 transform 動畫和 opacity 屬性的元素;
- position: fixed;
- will-change;
- filter;
結論
- 堅持使用 transform 和 opacity 屬性更改來實現動畫。
- 使用 will-change 或 translateZ 提升元素爲合成層。
- 避免過度使用提升規則;各層都需要內存和管理開銷。
事實上單獨計算合成層的內存耗費是挺大的。你可以打開 safari 的控制檯可以看到每個層的佔用。
如圖所見渲染這麼一個 fixed 的區域,要 GPU 喫 1.69 MB的內存,如果層數多,渲染面積大,屏幕倍率高,那麼要佔用的內存就多。
並且我有個有意思的發現,在 mac retina 屏幕上看這個層佔的 1.69 MB,而在一倍的外接顯示屏上才佔 700 kb。