在 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(例如 transform、will-change),在進入繪製前還會建立 PaintLayer,這個產生 layer 的子流程稱為 Update Layer Tree。
Paint#
繪製(Paint)又拆成兩個階段。
第一階段:產生繪製指令#
這個階段不會真的畫東西,而是把 Layout tree 轉成一連串繪製步驟,概念類似 Canvas,例如「在 (x, y) 畫一個 (w, h) 的紅色矩形」。每一條指令稱為 DisplayItem。
由於 stacking order(受 position、z-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