返回文章列表

Browser Rendering Pipeline:瀏覽器渲染流程

15 分鐘
前端Web

當瀏覽器載入一個網頁時,從收到 HTML 到畫面顯示,中間經歷了一連串的處理步驟,這個過程稱為 Rendering Pipeline (渲染流程)。

理解渲染流程,能幫助你寫出更高效能的前端程式碼,避免不必要的效能瓶頸。

無論採用 CSR、SSR 還是 SSG,瀏覽器最終都要走過這套流程,差別只在於初始 HTML 帶不帶有完整內容。


1. 解析 HTML → DOM Tree

瀏覽器收到 HTML 後,開始從上到下解析,將 HTML 標籤轉換成 DOM Tree (文件物件模型樹)。

解析過程中如果遇到 <script> 標籤,預設會暫停 HTML 解析,等 JavaScript 下載並執行完畢才繼續。這就是為什麼 <script> 通常放在 <body> 底部,或使用 defer / async 屬性。

HTML








2. 解析 CSS → CSSOM Tree

瀏覽器同步解析 CSS,建立 CSSOM Tree (CSS 物件模型樹)。

CSSOM 建立完成之前,瀏覽器不會進行渲染,因此 CSS 是渲染阻塞資源 (Render-Blocking Resource)。

<link> 標籤放在 <head> 中,讓 CSS 盡早開始下載,可以減少渲染阻塞的時間。


3. 合併 → Render Tree

DOM Tree 和 CSSOM Tree 合併後,形成 Render Tree (渲染樹)。

Render Tree 只包含需要顯示的節點:

  • display: none 的元素不會出現在 Render Tree 中
  • visibility: hidden 的元素會出現在 Render Tree 中 (佔據空間但不顯示)
  • <head><script> 等不可見的元素也不會出現

4. Layout

Layout (也稱為 Reflow) 階段,瀏覽器根據 Render Tree 計算每個元素的位置和尺寸。

這個階段確定:

  • 元素的寬高
  • 元素在頁面上的位置
  • 元素之間的相對關係

Layout 是相對耗費資源的操作,因為改變一個元素的尺寸可能會影響其他元素的排列。


5. Paint

Paint (也稱為 Repaint) 階段,瀏覽器將每個元素繪製成像素,包括:

  • 背景顏色
  • 文字
  • 邊框
  • 陰影

Paint 分為多個層 (Layers) ,瀏覽器會對複雜的元素 (例如有 transformopacity 的元素) 建立獨立的層。


6. Compositing

Compositing (合成) 階段,瀏覽器將各個層按照正確的順序合併,輸出最終的畫面。

擁有獨立合成層的元素 (例如使用 transform: translateZ(0)will-change: transform) 可以在 GPU 上獨立處理,不需要觸發 Layout 和 Paint,效能最好。


Reflow 與 Repaint 的效能影響

Reflow (Layout 重新計算)

以下操作會觸發 Reflow,效能影響最大:

  • 修改元素的尺寸 (widthheightpaddingmargin)
  • 新增或移除元素
  • 修改字體大小
  • 讀取某些屬性 (offsetWidthoffsetHeightgetBoundingClientRect())

讀取這些屬性會強制瀏覽器立即計算 Layout,即使還沒有渲染。

Repaint (重新繪製)

修改視覺樣式但不影響尺寸和位置,只觸發 Repaint,效能影響較小:

  • color
  • background-color
  • visibility
  • box-shadow

只觸發 Compositing

效能影響最小,只在 GPU 合成層上操作:

  • transform
  • opacity

減少 Reflow 的實務建議

批次修改樣式

JavaScript
// 不好:每次修改都可能觸發 Reflow
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';

// 好:一次修改,只觸發一次 Reflow
element.className = 'new-style';
// 或
element.style.cssText = 'width: 100px; height: 200px; margin: 10px;';

避免在迴圈中讀取和修改

JavaScript
// 不好:讀取和寫入交替,強制多次 Reflow
for (let i = 0; i < items.length; i++) {
  items[i].style.width = container.offsetWidth + 'px'; // 每次都讀取 offsetWidth
}

// 好:先讀取,再批次寫入
const containerWidth = container.offsetWidth; // 只讀取一次
for (let i = 0; i < items.length; i++) {
  items[i].style.width = containerWidth + 'px';
}

使用 transform 代替改變位置

JavaScript
// 會觸發 Reflow
element.style.left = '100px';

// 只觸發 Compositing,效能更好
element.style.transform = 'translateX(100px)';

總結

渲染流程:

效能影響由大到小:Reflow > Repaint > Compositing

寫高效能的前端程式碼,核心原則是:盡量避免觸發 Reflow,優先使用只觸發 Compositing 的屬性 (transformopacity)。

瀏覽器渲染是前端應用完整執行週期的最後一段。不同渲染策略 (CSR / SSR / SSG) 決定了初始 HTML 的內容,參考 CSR vs. SSR vs. SSG;想了解完整執行週期,參考 從原始碼到畫面