隨著頁(yè)面效果越來(lái)越炫酷,頁(yè)面被載入的動(dòng)畫(huà)量也逐步提升,對(duì)設(shè)備的壓力也就越來(lái)越大;開(kāi)發(fā)中,如果不講究些技巧,頁(yè)面常有卡頓現(xiàn)象,影響了用戶(hù)體驗(yàn);因此,前端們又開(kāi)始挖掘好用的辦法,如啟動(dòng)設(shè)備的硬件加速功能。
# 從一個(gè)動(dòng)畫(huà)開(kāi)始分析
? 【說(shuō)明】案例中沒(méi)有提供適配前綴如 -webkit-,請(qǐng)自行添加
<div>
<span></span>
</div>
div {
position: relative;
}
span {
display: block;
position: absolute;
top: 0;
left: 0;
width: 15px;
height: 15px;
border: 5px solid antiquewhite;
border-radius: 50%;
animation: run-around 4s infinite;
}
@keyframes run-around {
0%,100% {
top: 0;
left: 0;
}
25% {
top: 0;
left: 150px;
}
50% {
top: 150px;
left: 150px;
}
75% {
top: 150px;
left: 0;
}
}
? 效果為一個(gè)小圓環(huán)在做畫(huà)方運(yùn)動(dòng)。
【分析】打開(kāi) Chrome 的開(kāi)發(fā)者工具徐那種Timeline,可以看到如下信息:

? 這是動(dòng)畫(huà)從 0%-25% 的執(zhí)行過(guò)程。能發(fā)現(xiàn),整個(gè)過(guò)程都是比較耗時(shí)且 CPU 頻繁的凸起小山峰,把鼠標(biāo)移動(dòng)到淺綠色帶有毫秒時(shí)間的欄目時(shí)能看到 FPS 也大致在 20-50fps 之間,對(duì)于移動(dòng)設(shè)備,達(dá)到60fps是流暢的效果的標(biāo)準(zhǔn);
? 然后,我們?cè)倏纯磮?zhí)行過(guò)程渲染樹(shù)的變化:

? 可以看出,瀏覽器在不停的重繪 repaint,這當(dāng)然不是我們想要的結(jié)果,也難怪會(huì)出現(xiàn)卡頓現(xiàn)象。
?
# 卡頓了怎么辦
? 我們換做【CSS3動(dòng)畫(huà)】中的 transform 來(lái)看看效果:
@keyframes ball-around {
0%,100% {
transform: translate(0,0);
}
25% {
transform: translate(0,100px);
}
50% {
transform: translate(100px,100px);
}
75% {
transform: translate(100px,0);
}
}
【分析】看看 Timeline

? 整個(gè)過(guò)程中 CPU 占用基本為空,也能穩(wěn)居一個(gè)比較高的幀數(shù),保持這動(dòng)畫(huà)的流暢運(yùn)行。
? 再看看 Event Log

? 動(dòng)畫(huà)演示期間并沒(méi)有過(guò)多的 repaint 操作;
? 或許你不服氣,因?yàn)樵谌罩玖送瑯涌吹搅艘灿卸鄠€(gè)Paint操作,為什么就說(shuō)沒(méi)有過(guò)多的 repaint操作,OK,沒(méi)關(guān)系,那既然重繪是瀏覽器干的,瀏覽器它肯定知道頁(yè)面元素什么時(shí)候/哪里被重繪了,我們可以讓它幫我們標(biāo)記出來(lái):
? 按 ESC 打開(kāi) Rendering 面板,如果沒(méi)有就到More tools里面添加打開(kāi),選中Paint Flashing 和 FPS Meter

? 看注釋?zhuān)傻弥?Paint Flashing是用高亮形式標(biāo)記出頁(yè)面中需要重繪的地方, 再回退代碼到由top和left實(shí)現(xiàn)的方案,可以看到如下展示:

圓環(huán)位置被高亮框覆蓋這,也就是說(shuō),該方案下這個(gè)圓環(huán)是一直被重繪的,而使用transform動(dòng)畫(huà)這沒(méi)有被標(biāo)注,如下

? 另外,我們還注意到頁(yè)面上多了一個(gè)面板,可以看到電腦時(shí)時(shí)的幀數(shù)信息和GPU信息,即 顯卡信息,我們利用顯卡幫我們處理圖形問(wèn)題,當(dāng)然會(huì)比DOM操作來(lái)的流暢。

?
# 硬件加速的工作原理
? 瀏覽器接收到一個(gè)頁(yè)面之后,將html解析成DOM樹(shù),瀏覽器解析渲染「html」的過(guò)程 按著一定的規(guī)則執(zhí)行,DOM樹(shù)和CSS樹(shù)結(jié)合后構(gòu)成瀏覽器形成頁(yè)面的 渲染樹(shù) ; 渲染樹(shù)中包含大量的渲染元素,每一個(gè)元素會(huì)被分配到一個(gè)圖層中,每個(gè)圖層又會(huì)被加載到GPU形成渲染紋理,而圖層在GPU中 transform 是不會(huì)觸發(fā) repaint 的,這一點(diǎn)非常類(lèi)似3D繪圖功能,最終這些使用 transform 的圖層都會(huì)由獨(dú)立的合成器進(jìn)程進(jìn)行處理。也正因?yàn)檫@個(gè)原因,避免了頻繁 repaint。
? 繼續(xù)看我們的牛叉的開(kāi)發(fā)這工具。還是剛才的位置,打開(kāi) Rendering 面板,勾選layer border

? 我們能很清楚的看到頁(yè)面有那些圖層,我們的小環(huán),也占用了一個(gè)圖層。而top-left方案則沒(méi)有。

哪些情況會(huì)創(chuàng)建圖層呢?
- 3D 或者 CSS transform
- <video> 和 <canvas> 標(biāo)簽
- CSS filters
- 使用
z-index時(shí),發(fā)生元素覆蓋
? 再想想我們的動(dòng)畫(huà),其實(shí)都是2D的,并不是3D的。沒(méi)錯(cuò),所以我們才會(huì)在 Event Log 中看到多個(gè) Paint,其實(shí)是開(kāi)始動(dòng)畫(huà)時(shí)有一個(gè)(create),結(jié)束動(dòng)畫(huà)時(shí)有一個(gè)(delete)。當(dāng)循環(huán)執(zhí)行動(dòng)畫(huà)時(shí),每一個(gè)周期會(huì)發(fā)生兩次Paint,這也是為什么我們說(shuō) 沒(méi)有發(fā)生太多paint 的原因。相比與top-left方案的每一幀觸發(fā)一次Paint來(lái)說(shuō),效果已經(jīng)非常明顯。
那為什么又會(huì)有兩次Paint呢?
? 則是瀏覽器渲染設(shè)計(jì)的區(qū)別所在。瀏覽器在渲染前就創(chuàng)建3D transform 的獨(dú)立復(fù)合圖層,而在運(yùn)行期間才為 2D transform 創(chuàng)建。因此,動(dòng)畫(huà)開(kāi)始時(shí),生成新的復(fù)合圖層并加載為GPU的紋理用于初始化 repaint ,然后由GPU的復(fù)合器操縱整個(gè)動(dòng)畫(huà)的執(zhí)行。最后當(dāng)動(dòng)畫(huà)結(jié)束時(shí),再次執(zhí)行 repaint 操作刪除復(fù)合圖層。
?
# 使用 GPU 渲染元素
? 或許你會(huì)想,既然GPU加速這么好用,那我全用GPU好了,但實(shí)際上只有少數(shù)的幾個(gè)可以觸發(fā)GPU的硬件加速
- transform
- opacity
- filter
# 提高GPU的使用率,強(qiáng)制使用GPU渲染
? 因?yàn)?code>2D transform 依舊會(huì)發(fā)生兩次 Paint 操作,我們其實(shí)可以避免這種現(xiàn)象產(chǎn)生:
.transform1 {
transform: translateZ(0);
}
.transform2 {
transform: rotateZ(360deg);
}
? 強(qiáng)行設(shè)置 3D transform ,瀏覽器識(shí)別到這是一個(gè)3D動(dòng)畫(huà),渲染前就創(chuàng)建了一個(gè)獨(dú)立圖層,圖層中的動(dòng)畫(huà)則有GPU進(jìn)行預(yù)處理并出發(fā)了硬件加速。
? 如果一個(gè)元素的背后是一個(gè)復(fù)雜的元素,那么該元素的 repaint 操作就會(huì)浪費(fèi)大量的設(shè)備資源,我們就可以用上方的技巧強(qiáng)制出發(fā)GPU加速來(lái)減少性能的開(kāi)銷(xiāo)。
第一個(gè)動(dòng)畫(huà)的擴(kuò)展
? 假設(shè)我們的頁(yè)面有個(gè)全屏的被模糊過(guò)的背景圖片,然后界面上有個(gè)簡(jiǎn)單的動(dòng)畫(huà)。此時(shí),無(wú)論是 top-left 方案,還是 2D transform,都會(huì)觸發(fā)repaint,這就造成背景圖片也被 repaint;這有多坑相信大家都能想得到。
? 但是,如果我們利用 transform 技巧重構(gòu)這一效果,將模糊過(guò)的大背景圖存放到另一個(gè)圖層中,就不會(huì)再受動(dòng)畫(huà)影響重新出發(fā) Paint 事件了。
# 使用硬件加速的注意事項(xiàng)
? 使用硬件加速并不是十全十美的事情
- 過(guò)度使用引發(fā)的內(nèi)存問(wèn)題。
- 使用GPU渲染會(huì)影響字體的抗鋸齒效果。這是因?yàn)镚PU和CPU具有不同的渲染機(jī)制。即使最終硬件加速停止了,文本還是會(huì)在動(dòng)畫(huà)期間顯示得很模糊。
瀏覽器提出了一個(gè) will-change 屬性,該屬性允許平臺(tái)開(kāi)發(fā)者告知瀏覽器哪一個(gè)屬性即將發(fā)生變化,從而為瀏覽器對(duì)該屬性進(jìn)行優(yōu)化提供了時(shí)間,但兼容性不是很好
.example {
will-change: transfrom;
}
# 后語(yǔ)
? 本文帶有大量個(gè)人理解,如有不同意見(jiàn)和建議,歡迎交流