一、瀏覽器如何渲染網頁
要了解瀏覽器渲染頁面的過程,首先得知道一個名詞——關鍵路徑渲染。關鍵渲染路徑(Critical Rendering Path)是指與當前用戶操作有關的內容。例如用戶在瀏覽器中打開一個頁面,其中頁面所顯示的東西就是當前用戶操作相關的內容,也就是瀏覽器從服務器那收到的HTML,CSS,JavaScript等相關資源,然后經過一系列處理后渲染出來的web頁面。
瀏覽器渲染的過程主要包括以下五步:
① 瀏覽器將獲取的HTML文檔解析成DOM樹
② 處理CSS標記,構成層疊樣式表模型CSSOM(CSS Object Model)
③ 將DOM和CSSOM合並為渲染樹(rendering tree)將會被創建,代表一系列將被渲染的對象
④ 渲染樹的每個元素包含的內容都是計算過的,它被稱之為布局layout。瀏覽器使用一種流式處理的方法,只需要一次pass繪制操作就可以布局所有的元素
⑤ 將渲染樹的各個節點繪制到屏幕上,這一步被稱為繪制painting
圖形說明:
需要注意的是,以上五個步驟並不一定一次性順序完成,比如DOM或CSSOM被修改時,亦或是哪個過程會重復執行,這樣才能計算出哪些像素需要在屏幕上進行重新渲染。而在實際情況中,JavaScript和CSS的某些操作往往會多次修改DOM或者CSSOM。
二、瀏覽器渲染網頁的具體流程
1、構建DOM樹
當瀏覽器客戶端從服務器那接受到HTML文檔后,就會遍歷文檔節點然后生成DOM樹,DOM樹結構和HTML標簽一一對應。需要注意記下幾點:
① DOM樹在構建的過程中可能會被CSS和JS的加載而執行阻塞。
② display:none 的元素也會在DOM樹中。
③ 注釋也會在DOM樹中
④ script標簽會在DOM樹中
2、CSS解析
瀏覽器會解析CSS文件並生成CSS規則樹,在過程中,每個CSS文件都會被分析成StyleSheet對象,每個對象都包括CSS規則,CSS規則對象包括對應的選擇器和聲明對象以及其他對象。
在這個過程需要注意的是:
① CSS解析可以與DOM解析同進行
② CSS解析與script的執行互斥
③ 在Webkit內核中進行了script執行優化,只有在JS訪問CSS時才會發生互斥
3、構建渲染樹(Rendr tree construction)
通過DOM樹和CSS規則樹,瀏覽器就可以通過這兩個構建渲染樹了。瀏覽器會先從DOM樹的根節點開始遍歷每個可見節點,然后對每個可見節點找到適配的CSS樣式規則並應用。具體的規則有以下幾點需要注意:
① Render Tree和DOM Tree不完全對應
② display: none的元素不在Render Tree中
③ visibility: hidden的元素在Render Tree中
4、渲染樹布局(layout of the render tree)
布局階段會從渲染樹的根節點開始遍歷,由於渲染樹的每個節點都是一個Render Object對象,包含寬高,位置,背景色等樣式信息。所以瀏覽器就可以通過這些樣式信息來確定每個節點對象在頁面上的確切大小和位置,布局階段的輸出就是我們常說的盒子模型,它會精確地捕獲每個元素在屏幕內的確切位置與大小。需要注意的是:
① float元素,absoulte元素,fixed元素會發生位置偏移
② 我們常說的脫離文檔流,其實就是脫離Render Tree
5、渲染樹繪制(Painting the render tree)
在繪制階段,瀏覽器會遍歷渲染樹,調用渲染器的paint()方法在屏幕上顯示其內容。渲染樹的繪制工作是由瀏覽器的UI后端組件完成的。
三、瀏覽器渲染網頁的那些事兒
1、阻塞渲染
現代瀏覽器總是並行加載自語言。例如當HTML解析器被腳本阻塞時,解析器雖然會停止構建DOM,但仍然會辨識該腳本后面的資源,並進行預加載。且由於以下兩點。瀏覽器會延遲 JavaScript 的執行和 DOM 構建:
① CSS 被默認被視為阻塞渲染的資源,因此瀏覽器將在 CSSOM 構建完畢前不會渲染任何已處理的內容。
② JavaScript 不僅可以讀取和修改 DOM 屬性,還可以讀取和修改 CSSOM 屬性,因此CSS解析與script的執行互斥。
正是由於以上這些原因,script標簽的位置很重要我們在實際開發中應該盡量堅持以下兩個原則:
- 在引入順序上,CSS 資源先於 JavaScript 資源。
- JavaScript 應盡量少的去影響 DOM 的構建
2、回流和重繪(reflow和repaint)
HTML默認是流式布局的,但CSS和JS會打破這種布局,改變DOM的外觀樣式以及大小和位置。因此我們就需要知道兩個概念:
① reflow(回流):當瀏覽器發現某個部分發生了變化從而影響了布局,這個時候就需要倒回去重新渲染,大家稱這個回退的過程叫 reflow。
常見的reflow是一些會影響頁面布局的操作,諸如Tab,隱藏等。reflow 會從 html 這個 root frame 開始遞歸往下,依次計算所有的結點幾何尺寸和位置,以確認是渲染樹的一部分發生變化還是整個渲染樹。reflow幾乎是無法避免的,因為只要用戶進行交互操作,就勢必會發生頁面的一部分的重新渲染,且通常我們也無法預估瀏覽器到底會reflow哪一部分的代碼,因為他們會相互影響。
② repaint(重繪): repaint則是當我們改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部布局的屬性時,屏幕的一部分要重畫,但是元素的幾何尺寸和位置沒有發生改變。
需要注意的是,display:none 會觸發 reflow,而visibility: hidden屬性則並不算是不可見屬性,它的語義是隱藏元素,但元素仍然占據着布局空間,它會被渲染成一個空框,這在我們上面有提到過。所以 visibility:hidden 只會觸發 repaint,因為沒有發生位置變化。
我們不能避免reflow,但還是能通過一些操作來減少回流:
① 用transform做形變和位移
② 通過絕對位移來脫離當前層疊上下文,形成新的Render Layer
有些情況下,比如修改了元素的樣式,瀏覽器並不會立刻reflow 或 repaint 一次,而是會把這樣的操作積攢一批,然后做一次 reflow,這又叫異步 reflow 或增量異步 reflow。但是在有些情況下,比如 resize 窗口,改變了頁面默認的字體等。對於這些操作,瀏覽器會馬上進行 reflow。
3、幾條關於優化渲染效率的建議
① 合法地去書寫 HTML 和 CSS ,且不要忘了文檔編碼類型。
② 樣式文件應當在 head 標簽中,而腳本文件在 body 結束前,這樣可以防止阻塞的方式。
③ 簡化並優化CSS選擇器,盡量將嵌套層減少到最小。
④ 盡量減少在 JavaScript 中進行DOM操作。
⑤ 修改元素樣式時,更改其class屬性是性能最高的方法。
⑥ 盡量用 transform 來做形變和位移
參考: