在我們日常使用瀏覽器瀏覽網頁時,往往忽略了瀏覽器背后復雜的渲染過程。從輸入 URL 到頁面最終顯示在屏幕上,瀏覽器需要經過一系列精心設計的步驟。
瀏覽器渲染的整體流程

瀏覽器的渲染流程可以大致分為兩個主要部分:網絡 和 渲染。當用戶在地址欄輸入 URL 并按下回車鍵時,瀏覽器的網絡線程會發送 HTTP 請求與服務器通信,獲取 HTML 文檔。獲取到 HTML 文檔后,瀏覽器會將其封裝成一個渲染任務,并將其傳遞給渲染主線程的消息隊列。在事件循環機制的作用下,渲染主線程取出消息隊列中的渲染任務,開啟渲染流程。

渲染流程主要分為以下幾個階段:
- 解析 HTML:生成 DOM 樹。
- 樣式計算:計算每個 DOM 節點的最終樣式。
- 布局:計算每個節點的幾何信息。
- 分層:將頁面劃分為多個圖層。
- 生成繪制指令:為每個圖層生成繪制指令集。
- 分塊:將每個圖層劃分為更小的塊。
- 光柵化:將每個塊轉換為位圖。
- 繪制:將位圖繪制到屏幕上。
1. 解析 HTML

解析 HTML 是渲染流程的第一步,其目標是將 HTML 字符串解析為 DOM 樹。瀏覽器接收到 HTML 文件后,會將其從字節數據轉換為字符串,然后通過詞法分析將其轉換為標記(token)。標記化后的數據將被用來構建 DOM 樹。
在解析 HTML 的過程中,瀏覽器可能會遇到 <style>
、<link>
和 <script>
標簽。為了提高解析效率,瀏覽器會啟動一個預解析線程,優先下載外部 CSS 文件和外部 JS 文件。如果主線程解析到 <link>
標簽時,外部 CSS 文件尚未下載解析完成,主線程不會等待,而是繼續解析后續的 HTML。這是因為 CSS 的下載和解析工作是在預解析線程中進行的,這也是 CSS 不會阻塞 HTML 解析的根本原因。
然而,如果主線程解析到 <script>
標簽時,會停止解析 HTML,等待 JS 文件下載完成,并執行全局代碼后,才會繼續解析 HTML。這是因為 JS 代碼的執行可能會修改當前的 DOM 樹,所以 DOM 樹的生成必須暫停。這就是 JS 會阻塞 HTML 解析的根本原因。
2. 樣式計算
擁有了 DOM 樹后,瀏覽器還需要為每個節點計算最終的樣式。這一過程稱為樣式計算。主線程會遍歷 DOM 樹,為每個節點計算出其最終的樣式,生成樣式規則樹。在這一過程中,預設值會變成絕對值,相對單位會變成絕對單位。
3. 布局
樣式計算完成后,瀏覽器需要計算每個節點的幾何信息,這一過程稱為布局。主線程會遍歷 DOM 樹,根據計算樣式計算出每個節點的幾何信息,生成布局樹。布局樹上每個節點會有其在頁面上的 x、y 坐標以及盒子大小的具體信息。
需要注意的是,布局樹與 DOM 樹并非一一對應。例如,display: none
的節點不會生成到布局樹中,而偽元素選擇器雖然在 DOM 樹中不存在,但會生成到布局樹中。
4. 分層
布局完成后,瀏覽器會進行分層。分層的目的是為了提高渲染效率。主線程會遍歷布局樹,根據特定的規則(如滾動條、堆疊上下文、transform
、opacity
等)將頁面劃分為多個圖層。
5. 生成繪制指令
分層完成后,主線程會為每個圖層生成繪制指令集。這些指令集描述了如何繪制每個圖層的內容。生成繪制指令后,主線程的工作暫時告一段落,接下來將繪制信息提交給合成線程。
6. 分塊
合成線程接收到繪制信息后,會將每個圖層劃分為更小的塊。這一過程稱為分塊。分塊的目的是為了進一步提高渲染效率。
7. 光柵化
分塊完成后,合成線程會將塊信息交給 GPU 進程進行光柵化。光柵化的目的是將每個塊轉換為位圖,確定每個像素點的 RGB 信息。
8. 繪制
最后一步是繪制。合成線程會將每個圖層的位圖繪制到屏幕上。這一過程稱為繪制。
常見面試題
1. 什么是 reflow?
reflow 的本質是重新計算布局樹。當進行了會影響布局樹的操作后,需要重新計算布局樹,會引發 reflow。為了避免連續的多次操作導致布局樹反復計算,瀏覽器會合并這些操作,當 JS 代碼全部完成后再進行統一計算。所以,改動屬性造成的 reflow 是異步完成的。然而,當 JS 獲取布局屬性時,可能會造成無法獲取到最新的布局信息。為了避免這一問題,瀏覽器決定獲取屬性時立即 reflow。
2. 什么是 repaint?
repaint 的本質是重新根據分層信息計算繪制指令。當改動了可見樣式后,需要重新計算,會引發 repaint。由于元素的布局信息也屬于可見樣式,所以 reflow 一定會引起 repaint。
3. 為什么 transform 的效率高?
transform 的效率高是因為它既不會影響布局也不會影響繪制指令,它影響的只是渲染流程的最后一個「draw」階段。由于 draw 階段在合成線程中,所以 transform 的變化幾乎不會影響渲染主線程。反之,渲染主線程無論如何忙碌,也不會影響 transform 的變化。
總結
瀏覽器的渲染流程是一個復雜而精細的過程,涉及多個階段和多個線程的協同工作。從解析 HTML 到最終繪制頁面,每個階段都有其特定的任務和目標。了解瀏覽器的渲染流程不僅有助于我們更好地理解網頁是如何呈現的,還能幫助我們在開發中優化性能,減少不必要的重排和重繪。