瀏覽器渲染機(jī)制基礎(chǔ)

  1. 瀏覽器指的是Chrome系瀏覽器【Firefox大同小異,IE未知】
  2. 以下提到的“節(jié)點(diǎn)”、“標(biāo)簽”和“元素”不做區(qū)分,你懂就好

以前,前端面試?yán)飼r(shí)常會(huì)被問及或問別人:為什么樣式要放在head里,而script[src]標(biāo)簽放在最底部?面試者往往會(huì)回答:JS會(huì)阻塞頁面,CSS不會(huì)。

撇開面試的場景,我們可以再問一下自己,為什么這么放呢?阻塞頁面指的是什么阻塞,不會(huì)阻塞又指的是什么呢,甚至,CSS真的不阻塞嗎?

進(jìn)入CSS3的年代后,前端面試又多這樣的題:如何提升transform動(dòng)畫的性能?面試者也往往會(huì)回答:將2D變換屬性換成3D變換屬性,如將translate換成translate3d。

再撇開面試的場景,我們也再問一下自己,為什么這么做呢?也許你會(huì)說,使用3D可以告訴瀏覽器開啟GPU渲染,提升渲染性能。是的,那又為什么開啟了GPU渲染就可以提升性能呢?

要回答這些問題,單純從“別人告訴我的”是很難解釋清楚的,我們必須從瀏覽器自身去找尋答案。這就要從瀏覽器的渲染機(jī)制說起了……

兩類問題兩個(gè)階段,前者要從瀏覽器第一次渲染頁面講起,后者要從頁面變更后瀏覽器的渲染講起。

關(guān)鍵渲染路徑

瀏覽器從接收到頁面開始到頁面顯示,這整個(gè)過程中的所有步驟,稱為關(guān)鍵渲染路徑。用戶看到頁面實(shí)際上可以分為兩個(gè)階段:頁面內(nèi)容加載完成和頁面資源完成,分別對(duì)應(yīng)于DOMContentLoaded和Load。從DevTool-Network面板上看,如下圖:

DOMContentLoaded和Load

TL;DR
整個(gè)關(guān)鍵渲染路徑包括以下幾個(gè)步驟:

  1. 解析HTML,生成DOM樹(DOM)
  2. 解析CSS,生成CSSOM樹(CSSOM)
  3. 將DOM和CSSOM合并,生成渲染樹(Render-Tree)
  4. 計(jì)算渲染樹的布局(Layout)
  5. 將布局渲染到屏幕上(Paint)

從上面看出,我們并沒有提到腳本JS的處理,并不是腳本處理不在關(guān)鍵渲染路徑中,而是因?yàn)镴S的處理會(huì)對(duì)1、2產(chǎn)生影響,我們需要單獨(dú)去解釋。

DOM生成

瀏覽器在獲取HTML后,解析HTML代碼,將HTML的元素關(guān)系轉(zhuǎn)換成一個(gè)數(shù)據(jù)結(jié)構(gòu),就是我們所熟知的DOM(Document Object Model)。

在解析HTML過程中,會(huì)碰到幾類特殊的節(jié)點(diǎn)需要特殊的處理:

  1. style、link元素以及具有內(nèi)聯(lián)樣式的元素:交給“CSSOM生成”
  2. script(無論是否外鏈)元素:見“Script標(biāo)簽的處理”

P.S. 思考:碰到img、video、audio等資源性標(biāo)簽怎么辦?

解析完HTML,單純使用DOM,瀏覽器并不知道如何渲染這棵樹,DOM只是存儲(chǔ)了元素的關(guān)系,并沒有任何渲染信息,如寬高、顏色、背景、定位等。存儲(chǔ)這些信息,就需要CSSOM了。扒一張Chrome內(nèi)部文章的例子來總結(jié):

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
  </body>
</html>
DOM生成

CSSOM生成

上面簡單提到了,在HTML的解析過程中,會(huì)碰到style、link和內(nèi)聯(lián)樣式,這時(shí),瀏覽器會(huì)將解析DOM換成解析CSSOM,CSSOM和DOM是兩個(gè)獨(dú)立的數(shù)據(jù)結(jié)構(gòu)。

style和內(nèi)聯(lián)樣式

對(duì)這兩類樣式,瀏覽器會(huì)直接根據(jù)樣式聲明生成CSSOM,因?yàn)樗鼈儽旧砭椭苯雍袠邮絻?nèi)容。

link

對(duì)外聯(lián)樣式,瀏覽器會(huì)首先發(fā)送請求,待請求成功,獲取外聯(lián)樣式后,瀏覽器便會(huì)解析該外聯(lián)樣式,并生成相應(yīng)的CSSOM。

由于CSSOM負(fù)責(zé)存儲(chǔ)渲染信息,瀏覽器就必須保證在合成渲染樹之前,CSSOM是完備的,這種完備是指所有的CSS(內(nèi)聯(lián)、內(nèi)部和外部)都已經(jīng)下載完,并解析完,只有CSSOM和DOM的解析完全結(jié)束,瀏覽器才會(huì)進(jìn)入下一步的渲染,這就是傳說中的CSS阻塞渲染。

CSS阻塞渲染意味著,在CSSOM完備前,頁面將一直處理白屏狀態(tài),這就是為什么樣式放在head中,僅僅是為了更快的解析CSS,保證更快的首次渲染。

需要注意的是,即便你沒有給頁面任何的樣式聲明,CSSOM依然會(huì)生成,默認(rèn)生成的CSSOM自帶瀏覽器默認(rèn)樣式(default styles)。

樣式解析生成的CSSOM便含有渲染信息,這些信息會(huì)與DOM一起,生成渲染樹Render-Tree。最后,一樣附上Chrome官方的事例來個(gè)總結(jié):

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
CSSOM生成

在講渲染樹前,我們還需要講講一直被我們擱置的script。

Script標(biāo)簽的處理

JS可以操作DOM來修改DOM結(jié)構(gòu),可以操作CSSOM來修改節(jié)點(diǎn)樣式,這就導(dǎo)致了瀏覽器在解析HTML時(shí),一旦碰到script,就會(huì)立即停止HTML的解析(而CSS不會(huì)),執(zhí)行JS,再返還控制權(quán)。

事實(shí)上,JS執(zhí)行前不僅僅是停止了HTML的解析,它還必須等待CSS的解析完成。當(dāng)瀏覽器碰到script元素時(shí),發(fā)現(xiàn)該元素前面的CSS還未解析完,就會(huì)等待CSS解析完成,再去執(zhí)行JS。

JS阻塞了HTML的解析,也阻塞了其后的CSS解析,整個(gè)解析進(jìn)程必須等待JS的執(zhí)行完成才能夠繼續(xù),這就是所謂的JS阻塞頁面。一個(gè)script標(biāo)簽,推遲了DOM的生成、CSSOM的生成以及之后的所有渲染過程,從性能角度上講,將script放在頁面底部,也就合情合理了。

渲染樹

當(dāng)DOM和CSSOM構(gòu)建完成,它們一個(gè)存儲(chǔ)了節(jié)點(diǎn)信息,一個(gè)存儲(chǔ)了節(jié)點(diǎn)渲染信息,都不能直接用來渲染,為此瀏覽器會(huì)將兩者結(jié)合,生成渲染樹(Render-Tree),這棵樹就包含了頁面所有可見元素及其渲染信息。仍以上述同樣的例子:

渲染樹

生成渲染樹,瀏覽器做了這些工作:

  1. 從DOM的根節(jié)點(diǎn)開始,遍歷每個(gè)可視節(jié)點(diǎn):script、link、meta都屬于不可視節(jié)點(diǎn),另外,display: none的節(jié)點(diǎn)也屬于不可視節(jié)點(diǎn)
  2. 從CSSOM中搜索可視節(jié)點(diǎn)的樣式
  3. 計(jì)算這些樣式,將計(jì)算值應(yīng)用到可視節(jié)點(diǎn)上

渲染樹生成后,還是沒有辦法渲染到屏幕上,渲染到屏幕需要得到各個(gè)節(jié)點(diǎn)的位置信息,這就需要布局(Layout)的處理了。

布局

渲染樹生成后,瀏覽器便可以根據(jù)渲染樹中的樣式信息,結(jié)合設(shè)備的屏幕信息,計(jì)算每個(gè)元素的位置和尺寸。

渲染

得到了渲染樹及其節(jié)點(diǎn)的布局信息,瀏覽器便可以將最終的頁面渲染到屏幕。

整個(gè)關(guān)鍵渲染路徑主要就包括了以上這些步驟,每個(gè)步驟的快慢都決定著頁面的性能,或者說網(wǎng)站的性能,因此,談到首屏或者首渲的性能優(yōu)化,就不得不從關(guān)鍵渲染路徑著手,每一步都是有或多或少的可優(yōu)化點(diǎn)。一些優(yōu)化建議什么的,就不在本文范圍了。

當(dāng)我們的頁面首渲完成后,會(huì)有很多頁面交互,例如:動(dòng)畫、用戶點(diǎn)擊、滾屏。所有的交互都會(huì)引發(fā)瀏覽器新的渲染操作,這些操作直接影響著用戶交互性能,Chrome官網(wǎng)里直接稱作渲染性能。

渲染流程

對(duì)于渲染,我們首先需要了解一個(gè)概念:設(shè)備刷新率。

設(shè)備刷新率是設(shè)備屏幕渲染的頻率,通俗一點(diǎn)就是,把屏幕當(dāng)作墻,設(shè)備刷新率就是多久重新粉刷一次墻面。基本我們平常接觸的設(shè)備,如手機(jī)、電腦,它們的默認(rèn)刷新頻率都是60FPS,也就是屏幕在1s內(nèi)渲染60次,約16.7ms渲染一次屏幕。

這就意味著,我們的瀏覽器最佳的渲染性能就是所有的操作在一幀16.7ms內(nèi)完成,能否做到一幀內(nèi)完成直接決定著渲染性,影響用戶交互,這就要求我們需要了解瀏覽器的一個(gè)渲染過程,包括了哪些操作。

完整的渲染流程由以下幾步組成:

完整的渲染流程
  • JS:渲染引擎會(huì)等待所有的JS操作完成,收集JS對(duì)DOM和CSSOM的操作結(jié)果
  • Style:樣式計(jì)算,計(jì)算交互引起的樣式變更,并應(yīng)用到相應(yīng)的節(jié)點(diǎn)上
  • Layout:布局,根據(jù)新的Style,計(jì)算出新的節(jié)點(diǎn)位置和尺寸信息
  • Paint:渲染,計(jì)算最終的渲染信息(與上述的關(guān)鍵渲染路徑-渲染好像不同,其實(shí)是一樣的,只是上面直接跳到了渲染到屏幕這一步),在實(shí)際的渲染中,瀏覽器會(huì)盡可能地在多個(gè)層上去渲染,這個(gè)層類似PS里的圖層概念
  • Composite:合成,將每個(gè)渲染層合并,生成最終的一層渲染畫面。Paint階段,每個(gè)層獨(dú)立渲染,并不關(guān)心與其他層之間的關(guān)系,Composite就需要將這些層以正確的關(guān)系合成,有點(diǎn)像PS的導(dǎo)出PNG。合成發(fā)生在GPU上

完整的渲染流程就這樣,但是,并不是所有的交互都要走一遍這個(gè)流程,事實(shí)上,從性能角度講,我們更希望的是每個(gè)交互都能省它幾個(gè)步驟。確實(shí),也是做得到的,不管你是無意識(shí)還是有意識(shí),某些交互可以省這么一兩個(gè)步驟,除了這種走遍天下的渲染流程,“缺胳膊少腿”的渲染流程有以下兩種:

  1. 缺Layout
缺少Layout的渲染流程

Layout是計(jì)算節(jié)點(diǎn)的布局信息——位置和尺寸,當(dāng)我們修改的樣式里不涉及布局,瀏覽器就會(huì)省略這個(gè)步驟。例如:color。通常來講,Layout是很耗渲染性能的,從性能優(yōu)化角度講,能避免Layout就避免。除了修改布局屬性會(huì)觸發(fā)Layout外,很多獲取布局信息的JS操作也會(huì)引發(fā)Layout,如offsetHeight,getComputedStyle。

  1. 缺Layout和Paint
缺少Layout和Paint的渲染流程

缺Layout我們知道,只要不觸發(fā)布局屬性修改及獲取布局信息就可以避免。而避免Paint呢,就是讓瀏覽器將渲染直接交給合成,目前transform和opacity兩類樣式屬性是可以直接跳過Paint的。至于將translate2D變3D,實(shí)際上是觸發(fā)了層提升,使得相應(yīng)的元素渲染可以在獨(dú)立層上與其他層并行處理,間接提升了渲染性能。至于別人說的觸發(fā)GPU加速,也只是因?yàn)楸恍陆艘粋€(gè)層。

似乎提升層來提升性能是個(gè)很不錯(cuò)的玩法,但是,你的硬件不是無限的,每多一個(gè)層,就會(huì)多一份內(nèi)存,因此,控制層數(shù),也是很重要的性能提升。
除了將2D變3D可以達(dá)到層的提升,現(xiàn)代瀏覽器也加上了一個(gè)新的樣式屬性,來“預(yù)先”告知瀏覽器,提升層來處理相應(yīng)元素的渲染,這個(gè)屬性名字也是很不錯(cuò)的:will-change

使用該屬性,你不必translate3d,只需要:

.my-class {
  will-change: transform;
}

當(dāng)然,兼容性是個(gè)問題,自行caniuse。

正因?yàn)閠ransform和opacity可以跳過Paint,并且可以在某種形式下告知瀏覽器優(yōu)先以GPU來渲染,才有了現(xiàn)代CSS動(dòng)畫推崇優(yōu)先使用transform,避免使用position、height等屬性的變更來處理動(dòng)畫。一些流行的動(dòng)畫庫,如iScroll、Swiper.js等,都是使用transform來處理位置偏移,而非top、left等,就是因?yàn)樾阅芨摺?/p>

OK,渲染機(jī)制就是這么個(gè)事,怎么做性能優(yōu)化,就要根據(jù)不同的渲染步驟,配相應(yīng)的策略,還是那句話,怎么做性能優(yōu)化,不是本文的目的。

問題

  1. HTML解析時(shí),碰到img、video、audio等資源性標(biāo)簽怎么辦?
  2. CSS和JS的請求是在什么時(shí)候?
  3. CSS真的不阻塞HTML解析嗎?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容