瀏覽器內(nèi)核拿到內(nèi)容后拜银,渲染步驟大致可以分為以下幾步:
解析HTML凛忿,構(gòu)建DOM樹
解析CSS膘流,生成CSS規(guī)則樹
合并DOM樹和CSS規(guī)則榨乎,生成render樹
布局render樹(Layout/reflow)盔夜,負責(zé)各元素尺寸负饲、位置的計算
繪制render樹(paint),繪制頁面像素信息
瀏覽器會將各層的信息發(fā)送給GPU喂链,GPU會將各層合成(composite)返十,顯示在屏幕上
如下圖:
HTML解析,構(gòu)建DOM
整個渲染步驟中衩藤,HTML解析是第一步吧慢。
簡單的理解,這一步的流程是這樣的:
瀏覽器解析HTML赏表,構(gòu)建DOM Tree
解析HTML到構(gòu)建出DOM當(dāng)然過程可以簡述如下:
Bytes → characters → tokens → nodes → DOM
譬如假設(shè)有這樣一個HTML頁面:(以下部分的內(nèi)容出自參考來源检诗,修改了下格式)
<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><img src="awesome-photo.jpg"></div>
</body>
</html>
瀏覽器的處理如下:
列舉其中的一些重點過程:
Conversion轉(zhuǎn)換:瀏覽器將獲得的HTML內(nèi)容(Bytes)基于他的編碼轉(zhuǎn)換為單個字符
Tokenizing分詞:瀏覽器按照HTML規(guī)范標準將這些字符轉(zhuǎn)換為不同的標記token。每個token都有自己獨特的含義以及規(guī)則集
Lexing詞法分析:分詞的結(jié)果是得到一堆的token瓢剿,此時把他們轉(zhuǎn)換為對象逢慌,這些對象分別定義他們的屬性和規(guī)則
DOM構(gòu)建:因為HTML標記定義的就是不同標簽之間的關(guān)系,這個關(guān)系就像是一個樹形結(jié)構(gòu)一樣
例如:body對象的父節(jié)點就是HTML對象间狂,然后段略p對象的父節(jié)點就是body對象
最后的DOM樹如下:
生成CSS規(guī)則
同理攻泼,CSS規(guī)則樹的生成也是類似。簡述為:
Bytes → characters → tokens → nodes → CSSOM
譬如style.css內(nèi)容如下:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
那么最終的CSSOM樹就是:
構(gòu)建渲染樹
當(dāng)DOM樹和CSSOM都有了后,就要開始構(gòu)建渲染樹了
一般來說忙菠,渲染樹和DOM樹相對應(yīng)的何鸡,但不是嚴格意義上的一一對應(yīng)
因為有一些不可見的DOM元素不會插入到渲染樹中,如head這種不可見的標簽或者display: none等
整體來說可以看圖:
渲染
有了render樹牛欢,接下來就是開始渲染骡男,基本流程如下:
圖中重要的四個步驟就是:
計算CSS樣式
構(gòu)建渲染樹
布局,主要定位坐標和大小傍睹,是否換行隔盛,各種position overflow z-index屬性
繪制,將圖像繪制出來
然后拾稳,圖中的線與箭頭代表通過js動態(tài)修改了DOM或CSS吮炕,導(dǎo)致了重新布局(Layout)或渲染(Repaint)
這里L(fēng)ayout和Repaint的概念是有區(qū)別的:
Layout,也稱為Reflow访得,即回流龙亲。一般意味著元素的內(nèi)容、結(jié)構(gòu)震鹉、位置或尺寸發(fā)生了變化俱笛,需要重新計算樣式和渲染樹
Repaint,即重繪传趾。意味著元素發(fā)生的改變只是影響了元素的一些外觀之類的時候(例如迎膜,背景色,邊框顏色浆兰,文字顏色等)磕仅,此時只需要應(yīng)用新樣式繪制這個元素就可以了.
回流的成本開銷要高于重繪,而且一個節(jié)點的回流往往回導(dǎo)致子節(jié)點以及同級節(jié)點的回流簸呈, 所以優(yōu)化方案中一般都包括榕订,盡量避免回流。
什么會引起回流蜕便?
1.頁面渲染初始化
2.DOM結(jié)構(gòu)改變劫恒,比如刪除了某個節(jié)點
3.render樹變化,比如減少了padding
4.窗口resize
5.最復(fù)雜的一種:獲取某些屬性轿腺,引發(fā)回流两嘴,
很多瀏覽器會對回流做優(yōu)化,會等到數(shù)量足夠時做一次批處理回流族壳,
但是除了render樹的直接變化憔辫,當(dāng)獲取一些屬性時,瀏覽器為了獲得正確的值也會觸發(fā)回流仿荆,這樣使得瀏覽器優(yōu)化無效贰您,包括
(1) offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 調(diào)用了getComputedStyle()或者IE的currentStyle
回流一定伴隨著重繪坏平,重繪卻可以單獨出現(xiàn)
所以一般會有一些優(yōu)化方案,如:
減少逐項更改樣式锦亦,最好一次性更改style舶替,或者將樣式定義為class并一次性更新
避免循環(huán)操作dom,創(chuàng)建一個documentFragment或div孽亲,在它上面應(yīng)用所有DOM操作坎穿,最后再把它添加到window.document
避免多次讀取offset等屬性。無法避免則將它們緩存到變量
將復(fù)雜的元素絕對定位或固定定位返劲,使得它脫離文檔流,否則回流代價會很高
注意:改變字體大小會引發(fā)回流
再來看一個示例:
var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
// 添加node栖茉,再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));
普通圖層和復(fù)合圖層
渲染步驟中就提到了composite概念篮绿。
可以簡單的這樣理解,瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復(fù)合圖層
首先吕漂,普通文檔流內(nèi)可以理解為一個復(fù)合圖層(這里稱為默認復(fù)合層亲配,里面不管添加多少元素,其實都是在同一個復(fù)合圖層中)
其次惶凝,absolute布局(fixed也一樣)吼虎,雖然可以脫離普通文檔流,但它仍然屬于默認復(fù)合層苍鲜。
然后思灰,可以通過硬件加速的方式,聲明一個新的復(fù)合圖層混滔,它會單獨分配資源
(當(dāng)然也會脫離普通文檔流洒疚,這樣一來,不管這個復(fù)合圖層中怎么變化坯屿,也不會影響默認復(fù)合層里的回流重繪)
可以簡單理解下:GPU中油湖,各個復(fù)合圖層是單獨繪制的,所以互不影響领跛,這也是為什么某些場景硬件加速效果一級棒乏德。