在 Performance 面板中,主執行緒(Main Thread)的時間軸涵蓋了最多資訊。從開始解析 HTML 到最終繪製出像素,瀏覽器到底依序做了哪些事?

建構視角的對應章節:瀏覽器渲染流程

概覽#

把瀏覽器更新畫面的工作展開,可以大致分為四個階段:

  • Parse
  • Layout
  • 繪製(Paint)
  • 合成(Composite)

Parse#

接收到 HTML 後瀏覽器開始解析,將原始位元流轉為字串、利用 tag 建出 token 與 node,最終組成 Document Object Model(DOM)。

過程中遇到 inline CSS 或 <link> 載入的 CSS 檔,會以類似流程建立 CSS Object Model(CSSOM)。

接著瀏覽器會以 CSSOM 為每個 DOM node 算出 ComputedStyle —— 包含所有 style property 與 value 的大物件,這個過程稱為 Recalculate Style。在 Elements 面板的 Computed 分頁可以看到單一 node 的 ComputedStyle,也可以透過 JavaScript 用 getComputedStyle(element) 取得。

Layout#

Layout 階段將 DOM 轉換為 Layout tree,結構與 DOM 接近,但會排除 <script><link>display: none; 等不可見的 node,並補上不在 DOM 裡的偽元素 ::before::after。建立出來的結構也常被稱為 Render Tree。

接著瀏覽器會走訪 Layout tree 計算每個 LayoutObject 的位置與大小:依 LayoutBlock、LayoutInline 等類型套用不同算法,字體、overflow、scrollbar、float、table、flex 排版、視窗大小等都是在這裡決定。

如果 LayoutObject 帶有特定 style property(例如 transformwill-change),在進入繪製前還會建立 PaintLayer,這個產生 layer 的子流程稱為 Update Layer Tree。

Paint#

繪製(Paint)又拆成兩個階段。

第一階段:產生繪製指令#

這個階段不會真的畫東西,而是把 Layout tree 轉成一連串繪製步驟,概念類似 Canvas,例如「在 (x, y) 畫一個 (w, h) 的紅色矩形」。每一條指令稱為 DisplayItem。

由於 stacking order(受 positionz-index 等影響),無法只走訪一次 Layout tree 就生出所有 DisplayItem,必須依 stacking order 多次走訪,最後組成 DisplayItemList。可以視為先把每個 LayoutObject 轉成 DisplayItem,再依 stacking order 排序。

第二階段:Raster#

第二階段依據 PaintLayer 與 DisplayItemList 產出多個 layer,每個 layer 都是一張點陣圖,每個像素以 RRGGBBAA 表示。

Composite#

繪出多個 layer 後由合成器(Compositor)依正確順序合併成最終畫面,再交給瀏覽器顯示。

完整流程回顧#

把中間穿插的步驟一併納入後,更完整的轉譯流程是:

  • Recalculate Style
  • Layout
  • Update Layer Tree
  • Paint
  • Composite Layers

在 Performance 面板的時間軸上也可以看到這幾個階段。

補充#

Layers 的目的#

合成層(Compositing Layer)的存在是為了重複利用上次的繪製結果,降低觸發 Layout、Paint 的次數。例如把某個區塊獨立到自己的 layer 後,可以對整個 layer 套動畫而完全不用重繪內容。

Tiling#

進入 Raster 階段時,layer 可能很大,並非全都在視野內。為了加快 rasterization,瀏覽器會把 layer 切成多個 tile 平行處理。

Impl Thread#

Raster、Tiling、Composite 都是在另一條執行緒(稱為 Impl thread,相當於合成器執行緒(Compositor Thread))上進行,不佔用主執行緒。也因此即使主執行緒被吃滿、點擊與輸入無回應,頁面通常還是能滾動。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10248720