抖音三面:硬件加速中的“層”和層疊上下文中的“層”,是一個東西嗎?
大家好,我是年年!這篇文章是關(guān)于瀏覽器渲染中“分層”與硬件加速的,我會講清 :
什么是硬件加速?
合成層的“層”與層疊上下文的“層”是一個東西嗎?
層爆炸、層壓縮是什么?
都說要減少回流、重繪,怎樣利用硬件加速做到?
頁面渲染的流程
首先來復(fù)習(xí)一下一個老八股:頁面渲染的流程, 簡單來說,初次渲染時會經(jīng)過以下幾步
構(gòu)建DOM樹;
樣式計算;
布局定位;
圖層分層;
圖層繪制;
合成顯示;
在CSS屬性改變時,重渲染會分為“回流”、“重繪”和“直接合成”三種情況,分別對應(yīng)從“布局定位”/“圖層繪制”/“合成顯示”開始,再走一遍上面的流程。
元素的CSS具體發(fā)生什么改變,則決定屬于上面哪種情況:
回流(又叫重排):元素位置、大小發(fā)生變化導(dǎo)致其他節(jié)點聯(lián)動,需要重新計算布局;
重繪:修改了一些不影響布局的屬性,比如顏色;
直接合成:合成層的transform、opacity修改,只需要將多個圖層再次合并,而后生成位圖,最終展示到屏幕上;
渲染中的層
上面提到了渲染過程中會發(fā)生“圖層分層”。瀏覽器中的層分為兩種:“渲染層”和“合成層(也叫復(fù)合層)”。很多文章中還會提到一個概念叫“圖形層”,其實可以把它當(dāng)作合成層看待。為了降低理解成本,本文全部使用“渲染層”和“合成層”這兩個名詞描述。
開發(fā)者工具中的Layers
先直觀的感受一下“層”,打開瀏覽器開發(fā)者工具的layers:
可以看到AB元素都在最底下的圖層中,元素C是單獨的一層,元素D又是一層。
<style>
body{
margin:0;
padding:0;
}
.box {
width: 100px;
height: 100px;
background: rgba(240, 163, 163, 0.4);
border: 1px solid pink;
border-radius: 10px;
text-align: center;
}
#a {
}
#b {
position: absolute;
top:0;
left: 80;
z-index: 2;
}
#c {
position: absolute;
top:0;
left: 160;
z-index: 3;
transform: translateZ(0);
}
#d {
position: absolute;
top:0;
left: 240;
z-index: 4;
}
.description {
font-size: 10px;
}
</style>
<div id="a" class="box">A</div>
<div id="b" class="box">
B
<div class="description">z-index:2</div>
</div>
<div id="c" class="box">
C
<div class="description">z-index:3</div>
<div class="description">transform: translateZ(0)</div>
</div>
<div id="d" class="box">
D
<div class="description">z-index:4</div>
</div>
之前說過,瀏覽器中的層分兩種,渲染層和合成層,在這里看到的全部都是合成層。那么,怎樣生成一個渲染層,又怎樣才能形成一個合成層呢?
渲染層
渲染層的概念跟“層疊上下文”密切相關(guān),之前也寫過一篇文章,可以看這里。簡單來說,擁有z-index屬性的定位元素會生成一個層疊上下文,一個生成層疊上下文的元素就生成了一個渲染層。
還是沿用上面的例子,BCD三個元素都是擁有z-index屬性的定位元素(絕對定位),所以他們?nèi)齻€都形成了一個渲染層,加上document根元素形成的,一共是四個渲染層。(再強調(diào)一下,在開發(fā)者工具中看不到渲染層。)
形成渲染層的條件也就是形成層疊上下文的條件,有這幾種情況:
document 元素
擁有z-index屬性的定位元素(position: relative|fixed|sticky|absolute)
彈性布局的子項(父元素display:flex|inline-flex),并且z-index不是auto時
opacity非1的元素
transform非none的元素
filter非none的元素
will-change = opacity | transform | filter
此外需要剪裁的元素也會形成一個渲染層,也就是overflow不是visible的元素
合成層
在開發(fā)者工具中看到的不是渲染層,而是下面要講的合成層,只有一些特殊的渲染層才會被提升為合成層,通常來說有這些情況:
transform:3D變換:translate3d,translateZ;
will-change:opacity | transform | filter
對 opacity | transform | fliter 應(yīng)用了過渡和動畫(transition/animation)
video、canvas、iframe
可以看出,上面這些條件屬于生成渲染層的“加強版”,也就是說形成合成層的條件要更苛刻。
還是用開頭的例子,C元素就是命中條件1,使用了3D變換transform: translateZ(0),于是被提升到一個單獨的合成層。
但是D元素沒有命中上面任何一條規(guī)則,卻也是一個單獨的合成層。因為還有一種情況——隱式合成。
隱式合成
當(dāng)出現(xiàn)一個合成層后,層級順序高于它的堆疊元素就會發(fā)生隱式合成。
我們給C、D元素設(shè)置層級,z-index分別是3和4;又在C元素上使用3D變換,提升成了合成層。此時,層級高于它的D元素就發(fā)生了隱式合成,也變成了一個合成層。
隱式合成出現(xiàn)的根本原是,元素發(fā)生了堆疊,瀏覽器為了保證最后的展示效果,不得不把層級順序更高的元素拎出來蓋在已有合成層上面。
層爆炸與層壓縮
這是我在項目中實際遇到的一個問題:一個頁面在低端機器上滾動時非??D,排查了很久,最后發(fā)現(xiàn)原因就在于隱式合成帶來的層爆炸。
隱式合成產(chǎn)生了很多預(yù)期外的合成層——頁面中所有 z-index 高于它的節(jié)點全部被提升,這些合成層都是相當(dāng)消耗內(nèi)存和GPU的。所以帶給我們的啟示是給合成層一個大的z-index值,避免出現(xiàn)隱式合成。
還好瀏覽器逐漸進(jìn)行了優(yōu)化,也就是層壓縮機制——多個渲染層同一個合成層重疊時,會自動將他們壓縮到一起,避免“層爆炸”帶來的損耗。
硬件加速
上面講了這么多,在實際開發(fā)中有什么用呢?或者說,瀏覽器為什么要分層呢?答案是硬件加速。聽起來很厲害,其實不過是給HTML元素加上某些CSS屬性,比如3D變換,將其提升成一個合成層,獨立渲染。
之所以叫硬件加速,就是因為合成層會交給GPU(顯卡)去處理,在硬件層面上開外掛,比在主線程(CPU)上效率更高,。
就像在ipad上畫畫一樣,畫手都是在不同的圖層繪制線稿、上色,這樣才方便后期修改,不至于牽一發(fā)而動全身。提升成合成層的元素發(fā)生回流、重繪都只影響這一層,渲染效率得到提升。
來看一個例子,使用animation改變B元素的寬度,通過開發(fā)者工具Layers中的“paint count”的可以看到頁面繪制次數(shù)會一直在增加,能直觀感受到頁面發(fā)生了“重繪”。
可以注意到,重繪是發(fā)生在整個圖層#document上的,也就是整個頁面都要重繪。
<style>
.box {
width: 100px;
height: 100px;
background: rgba(240, 163, 163, 0.4);
border: 1px solid pink;
border-radius: 10px;
text-align: center;
}
#a {
}
#b {
position: absolute;
top: 50;
left: 50;
z-index: 2;
animation: width-change 5s infinite;
}
@keyframes width-change {
0% {
width: 80px;
}
100% {
width: 120px;
}
}
.description {
font-size: 10px;
}
</style>
<div id="a" class="box">A</div>
<div id="b" class="box">
B
<div class="description">animation:width-change</div>
</div>
給B元素加上will-change:transform開啟硬件加速,讓他提升成一個合成層。
會發(fā)現(xiàn)重繪只發(fā)生在這個圖層上,#document圖層的繪制次數(shù)不會一直增加了。
<style>
.box {
width: 100px;
height: 100px;
background: rgba(240, 163, 163, 0.4);
border: 1px solid pink;
border-radius: 10px;
text-align: center;
}
#a {
}
#b {
position: absolute;
top: 50;
left: 50;
z-index: 2;
animation: width-change 5s infinite;
will-change: transform;
}
@keyframes width-change {
0% {
width: 80px;
}
100% {
width: 120px;
}
}
.description {
font-size: 10px;
}
</style>
<div id="a" class="box">A</div>
<div id="b" class="box">
B
<div class="description">animation:width-change</div>
</div>
這就是硬件加速的意義:我們在講到性能優(yōu)化時,經(jīng)常會說減少回流、重繪,如果能直接避免當(dāng)然是最好,但如果實在沒法避免,可以使用硬件加速,讓這個元素單獨回流、重繪,減少繪制的面積。
有得必有失,開啟硬件加速后的合成層會交給GPU處理,當(dāng)圖層過多時,將會占用大量內(nèi)存,尤其在移動端會造成卡頓,讓優(yōu)化適得其反。正確使用硬件加速就是在渲染效率和性能損耗之間找到一個平衡點,讓頁面渲染迅速不白屏,又流暢絲滑。
優(yōu)化渲染性能
上面講到了,利用硬件加速,可以把需要重排/重繪的元素單獨拎出來,減少繪制的面積,除此之外,提升渲染性能還有這幾個常見的方法:
避免重排/重繪,直接進(jìn)行合成,合成層的transform 和 opacity的修改都是直接進(jìn)入合成階段的;比如可以使用transform:translate代替left/top修改元素的位置;使用transform:scale代替寬度、高度的修改;
注意隱式合成,給合成層一個較大的z-index值,雖然大部分瀏覽器已經(jīng)實現(xiàn)了層壓縮的能力,但是依舊有無法處理的情況,最好的辦法就是一開始就避免層爆炸;
減小合成層占用的內(nèi)存,合成層的最大問題就是占用內(nèi)存較多,而內(nèi)存的占用和元素的尺寸是成正比的,如果要實現(xiàn)一個100X100的元素,可以給寬高都設(shè)置為10px,再使用transform:scale(10)放大10倍,這樣占用的內(nèi)存只有直接設(shè)置的1/100;
結(jié)語
回到開頭的幾個問題,答案不難在文中找到:
硬件加速并不是前端專有的東西,它是一個很寬泛的計算機概念——把軟件的工作交給特定的硬件,更高效的完成某項任務(wù)。對于前端來說,就是使用特定的CSS屬性,把元素提升成合成層,交給GPU處理;
合成層中的“層”可以被認(rèn)為是真正物理上的層,瀏覽器把它獨立出來,單獨拿給GPU處理,而層疊上下文的“層”則是指渲染層,更像是一個概念上的層,一個合成層可以包含多個渲染層;
層爆炸指的是大量元素意料之外被提升成合成層,即隱式合成;層壓縮是瀏覽器對隱式合成的優(yōu)化,chrome在94版本中做到比較完善了;
使用transform、opacity取代傳統(tǒng)屬性來實現(xiàn)一些動畫,并把他們提升到一個單獨的合成層,能跳過布局計算和重新繪制,直接合成,能避免不必要的回流、重繪;
作者:年年
歡迎關(guān)注微信公眾號 :深圳灣碼農(nóng)