介紹
關鍵渲染路徑 (CRP) 是瀏覽器執行的將 HTML、CSS 和 JavaScript 轉換為螢幕上可見像素的步驟序列。優化此管道直接影響第一內容繪製 (FCP) 和 最大內容繪製 (LCP)。本文詳細介紹了每個 CRP 階段——DOM 構建、CSSOM 構建、渲染樹、佈局、繪製和合成——並提供了可行的最佳化策略。
第一階段:DOM 構建
當瀏覽器接收到 HTML 位元組時,它:
- 位元組→字元:將原始位元組解碼為字符
- 標記化:將字元轉換為標記(
StartTag、EndTag等) - Lexing:將令牌轉換為節點
- DOM Tree:建立保留父子關係的節點樹
<!-- This HTML produces a DOM tree with html > head + body > h1 + p -->
<!DOCTYPE html>
<html>
<head><title>Page</title></head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
最佳化:最小化 HTML 大小,透過正確的伺服器配置儘早交付,並使用 串流(例如 Transfer-Encoding: chunked)。
第 2 階段:CSSOM 構建
預設情況下,CSS 是渲染阻塞的。瀏覽器在渲染之前必須下載並解析所有CSS。 CSS 位元組與 HTML 經歷相同的轉換管道,產生 CSS 物件模型 (CSSOM)。
body { font-size: 16px; }
h1 { color: blue; }
每個 CSS 規則都與選擇器進行匹配,並在此階段計算特異性。
最佳化:
- 直接在
<head>中內嵌關鍵 CSS 以取得首頁內容 - 使用
media="print"或loading="lazy"延遲非關鍵 CSS - 縮小並使用 tree-shaking 刪除未使用的 CSS
第三階段:渲染樹
渲染樹結合了 DOM 和 CSSOM — 它只包含可見元素。排除具有 display: none 或 <head> 子元素的元素。每個渲染樹節點都包含其計算的樣式。
DOM CSSOM
| |
+----+-----+
|
Render Tree
(visible nodes + computed styles)
第四階段:佈局(回流焊接)
瀏覽器計算每個渲染樹節點的幾何(位置和大小)。這是從視窗開始的自上而下傳遞。具有基於百分比的寬度、flexbox 或 grid 的元素要求瀏覽器計算相對於其容器的大小。
佈局抖動的原因
重複強制同步佈局 - 在編寫樣式後讀取佈局屬性(例如 offsetHeight) - 導致瀏覽器在單一框架中多次重新計算佈局:
// ❌ Bad: Forces layout on each iteration
for (let i = 0; i < boxes.length; i++) {
const width = box.offsetWidth; // reads (triggers layout)
box.style.width = (width + 10) + 'px'; // writes
}
// ✅ Good: Batch reads, then batch writes
const widths = boxes.map(b => b.offsetWidth); // reads
boxes.forEach((b, i) => b.style.width = (widths[i] + 10) + 'px'); // writes
最佳化:
- 先讀取佈局屬性,然後寫入
- 使用
transform和opacity進行動畫(僅限合成器) - 避免複雜的 CSS 選擇器在回流時觸發選擇器匹配
- 使用
contain: layout style paint隔離子樹
第五階段:油漆
瀏覽器將像素填入圖層中—文字、顏色、圖像、邊框、陰影。這是昂貴的,因為它涉及光柵化。單獨的合成器層上的元素可以獨立繪製。
油漆觸發器
- 改為
color、background-color、visibility、outline - 添加陰影或文字裝飾
第六階段:複合
瀏覽器將繪製的圖層合併到 GPU 上的最終畫面。相對於佈局和繪製來說,合成成本較低。關鍵是盡量減少佈局和繪製工作,以便大多數更改只需要合成。
僅限合成器的屬性
| 物業 | 觸發器佈局 | 觸發油漆 | 僅合成器 |
|---|---|---|---|
| 佔位符_0 | 沒有 | 沒有 | 是的 |
| 佔位符_0 | 沒有 | 沒有 | 是的 |
| 佔位符_0 | 是的 | 是的 | 沒有 |
| 佔位符_0 | 是的 | 是的 | 沒有 |
阻塞資源與非阻塞資源
| 資源 | 預設行為 | 最佳化 |
|---|---|---|
CSS (<link>) | 渲染阻塞 | 內嵌關鍵,延遲非關鍵 |
JS <script> | 解析器阻塞 (DOM) | 使用 async 或 defer |
JS defer | 非阻塞,在 HTML 解析後執行 | 訂單已保留 |
JS async | 非阻塞,下載時執行 | 無訂單保證 |
<!-- ✅ Best practices -->
<script async src="analytics.js"></script>
<script defer src="app.js"></script>
<link rel="stylesheet" href="critical.css" />
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'" />
衡量 CRP 績效
Lighthouse 提供針對 CRP 的稽核:
- 消除渲染阻塞資源 — 辨識阻塞 CSS/JS
- 推遲螢幕外圖像 — 減少繪畫區域
- 最小化主執行緒工作 - 辨識長任務
- 減少 JavaScript 執行時間 — 有助於解析器阻塞腳本
運行:npx lighthouse https://example.com --preset=desktop
結論
優化關鍵渲染路徑需要了解 DOM/CSSOM 構造、渲染樹形成、佈局、繪製和合成的順序性質。關鍵策略包括內聯關鍵 CSS、對腳本使用 async/defer、透過批次 DOM 讀/寫避免佈局抖動,以及優先使用僅限合成器的動畫屬性。定期 Lighthouse 審核有助於識別 CRP 瓶頸並指導持續改進。

