渲染流程概述
渲染的目標:將HTML文本轉化為可以看到的像素點
當瀏覽器的網絡線程收到 HTML 文檔后,會產生一個渲染任務,并將其傳遞給渲染主線程
的消息隊列。在事件循環機制
的作用下,渲染主線程取出消息隊列中的渲染任務,開啟渲染流程。
渲染流程的階段(8個步驟):
HTML
解析- 樣式計算
- 布局
- 分層
- 繪制
- 分塊
- 光柵化
- 畫
每個階段的輸出將成為下一個階段的輸入,整個流程形成了一套高效的生產流水線。
1. 解析 HTML
第一步: 解析 HTML
- HTML 解析: 瀏覽器會解析 HTML 文件,遇到
CSS
會解析CSS
,遇到JS
會執行JS
。 - 外部資源的加載: 瀏覽器會啟動一個預解析線程,提前下載外部的
CSS
和JS
文件。
關鍵點:
link
標簽會在主線程解析到時,繼續解析 HTML,不會阻塞。
script
標簽會暫停 HTML 解析,等待 JavaScript 下載并執行完成后,才能繼續解析。
解析完成后,瀏覽器得到 DOM 樹 和 CSSOM 樹。
2. 樣式計算
第二步: 樣式計算
主線程會遍歷 DOM 樹,計算每個節點的最終樣式,稱之為 Computed Style。
這一過程存在轉換操作,預設值(例如
red
)轉換成絕對值(例如rgb(255, 0, 0)
),單位(例如em
)轉化為px
。
此步驟后,我們得到了一棵包含樣式的 DOM 樹。
3. 布局
第三步: 布局
布局階段,瀏覽器會根據 DOM 樹計算出每個節點的幾何信息,如寬高、位置等。
布局樹的特性(DOM樹和Layout樹不一定是一一對應的):
display: none
的節點不會出現在布局樹中。- 偽元素
::before
會出現在布局樹中,盡管 DOM 樹中沒有。
完成布局后,瀏覽器會生成 布局樹。
4. 分層
第四步: 分層
主線程會根據布局樹,按照一套復雜的策略進行分層。
為什么要分層:
如果某一層發生變化,后續只處理這一層,從而提升性能。
影響分層的因素包括:滾動條、transform
、opacity
等,也可以通過will-change
屬性更大程度的影響分層結果。
5. 繪制
第五步: 繪制
每一層的繪制指令集
會被生成,用于描述圖層如何呈現內容。
完成繪制后,主線程將繪制信息提交給 合成線程,剩下的工作由合成線程完成。
6. 分塊
第六步: 分塊
合成線程將每一層分成多個小塊,劃分為更小的區域。
這一步會從線程池中取出多個線程來完成分塊的工作。
7. 光柵化
第七步: 光柵化
合成線程將分塊信息交給 GPU 進程,通過多個線程快速處理,優先處理靠近視口的區域。光柵化后的結果是每個塊的位圖。
光柵化的過程中會啟動GPU進程進行加速。
8. 畫
最后一步: 畫
合成線程生成 指引(quad)信息,描述每個位圖如何繪制到屏幕的正確位置,并考慮旋轉、縮放等變形。
變形發生在合成線程,與渲染主線程無關,正是因為如此,
transform
樣式非常高效。
最終,合成線程將指引信息提交給 GPU 進程,通過 GPU 硬件渲染生成屏幕上的圖像。
總結
通過并行處理,瀏覽器能夠在多個線程中同時完成渲染任務,保證頁面能夠快速響應用戶交互,提高了整體性能。渲染流程的各個階段和線程池的協作使得瀏覽器能夠高效地繪制出用戶所看到的頁面,優化了體驗和性能。
加速技巧:
will-change
屬性可以提前告知瀏覽器需要優化的渲染層。- 使用
transform
和opacity
來避免頁面重排,提高渲染效率。