瀏覽器渲染原理(一文搞懂)

風不識途 發表於 2022-11-26

瀏覽器渲染原理

Pasted image 20221119110527

前言

瀏覽器的主要功能總結起來就是一句話:將使用者輸入的 URL 轉變成視覺化的影像。

  1. URLDOM 樹;
  2. DOM 樹到視覺化影像;
  3. 這兩個過程之間的關係並沒有那麼明確,我們可以統稱這兩個過程為頁面的渲染;

1. 網頁的解析過程

  • 思考一下:一個網頁從 url 輸入到瀏覽器,經歷過怎樣的解析過程?

Pasted image 20221118173743

  • 要想深入理解下載的過程,我們還要先理解,一個 index.html 被下載下來後是如何被解析和顯示在瀏覽器上的;

2. 瀏覽器的功能與組成

2.1 瀏覽器核心

  1. Trident ( 三叉戟): IE、 360 安全瀏覽器、 UC 瀏覽器
  2. Gecko( 壁虎) : Mozilla Firefox
  3. Presto(急板樂曲) -> Blink (眨眼): Opera
  4. Webkit : Safari、 360 極速瀏覽器、搜狗高速瀏覽器、移動端瀏覽器(Android、 iOS)
  5. Webkit -> Blink : Google Chrome, Edge

瀏覽器渲染原理(一文搞懂)

瀏覽器的核心相當於汽車的發動機,是最核心的存在,它負責將程式碼轉換成使用者眼中的介面。
  • 我們經常說的瀏覽器核心指的是瀏覽器的排版引擎:

    • 排版引擎 (layout engine),也稱為瀏覽器引擎 (browser engine)、頁面渲染引擎 (rendering engine) 或樣版引擎;
  • 也就是一個網頁下載下來後,就是由我們的渲染引擎來幫助我們解析的;

2.2 程式與執行緒

  • 程式與執行緒的解釋:

    • 程式:程式的一次執行,它佔有一片獨有的記憶體空間,是作業系統執行的基本單元;
    • 執行緒:是程式內的一個獨立執行單元,是 CPU 排程的最小單元;
  • 瀏覽器: 多程式、多執行緒模型

    • Browser 程式:

      • 瀏覽器的主程式,負責瀏覽器介面的顯示,和各個頁面的管理;
      • 瀏覽器中所有其他型別程式的祖先,負責其他程式的的建立和銷燬,它有且只有一個!
    • Renderer 程式:

      • 網頁渲染程式,負責頁面的渲染,可以有多個渲染程式的數量,不一定是開啟頁面的數量;
    • GPU Process :負責 GPU 相關;
    • Plugin Process:負責控制一個網頁用到的 Plugin

Pasted image 20221118175806

3. 瀏覽器渲染流程

3.1 渲染引擎解析過程

  • 渲染引擎在拿到一個頁面後,如何解析整個頁面並且最終呈現出我們的網頁呢?

    • 我們透過下面的這幅圖,讓我們更加詳細的學習它的過程;

Pasted image 20221116175343

3.2 渲染引擎主要模組

  • 一個渲染引擎主要包括:HTML 解析器,CSS 解析器,javascript 引擎,佈局 layout 模組,繪圖模組;

    • HTML 解析器:解釋 HTML 文件的解析器,主要作用是將 HTML 文字解釋成 DOM 樹;
    • CSS 解析器:它的作用是為 DOM 中的各個元素物件計算出樣式資訊,為佈局提供基礎設施;
    • Javascript 引擎:使用 Javascript 程式碼可以修改網頁的內容,也能修改 css 的資訊,javascript 引擎能夠解釋 javascript 程式碼,並透過 DOM 介面和 CSS 樹介面來修改網頁內容和樣式資訊,從而改變渲染的結果
    • 佈局 (layout):在 DOM 建立之後,Webkit 需要將其中的元素物件同樣式資訊結合起來,計算他們的大小位置等佈局資訊,形成一個能表達這所有資訊的內部表示模型
    • 繪圖模組 (paint):使用圖形庫將佈局計算後的各個網頁的節點繪製成影像結果

4. 渲染頁面的詳細流程

瀏覽器渲染頁面的整個過程:瀏覽器會從上到下解析文件。

Pasted image 20221116180210

4.1 HTML 解析過程

遇見 HTML 標記,呼叫 HTML 解析器解析為對應的 token (一個 token 就是一個標籤文字的序列化)並構建 DOM 樹(就是一塊記憶體,儲存著 tokens,建立它們之間的關係)

Pasted image 20221116191638

4.2 生成 CSS 規則

遇見 style/link 標記呼叫解析器處理 CSS 標記並構建 CSS 樣式樹。

Pasted image 20221116191833

4.3 構建 Render Tree

當有了 DOM Tree CSSOM Tree 後,就可以兩個結合來構建 Render Tree

Pasted image 20221116191924

  • 注意一link 元素不會阻塞 DOM Tree 的構建過程,但是會阻塞 Render Tree 的構建過程

    • 這是因為 Render Tree 在構建時,需要對應的 CSSOM Tree
  • 注意二Render TreeDOM Tree 並不是一一對應的關係

    • 比如對於 displaynone 的元素,壓根不會出現在 render tree 中;

4.4 佈局 (layout) 和繪製 (Paint)

  • 第四步是:在渲染樹(Render Tree)上執行佈局(Layout) 來計算每個節點的幾何體。

    • 渲染樹會表示顯示哪些節點以及其他樣式,但是不表示每個節點的尺寸、位置等資訊;
    • 佈局是確定呈現樹中所有節點的寬度、高度和位置資訊;
  • 第五步是:將每個節點繪製(Paint)到螢幕上。

    • 在繪製階段,瀏覽器將佈局階段計算的每個 frame 轉為螢幕上實際的畫素點;
    • 包括將元素的可見部分進行繪製,比如文字、顏色、邊框、陰影、替換元素(比如 img)

Pasted image 20221116192224

5. 重繪和迴流解析

5.1 重繪(Repaint)

  • 第一次渲染內容稱之為繪製(paint)

    • 之後重新渲染稱之為重繪
  • 重繪不會帶來重新佈局,所以並不一定伴隨重排。

    • 瀏覽器會根據元素的新屬性重新繪製,使元素呈現新的外觀。
  • 什麼屬性會導致重繪呢?

    • color background box-shadow.. (比如修改背景色、文字顏色、邊框顏色、樣式等)

5.2 迴流/重排(reflow)

理解迴流 reflow :也可以稱之為重排

  • 第一次確定節點的大小和位置,稱之為佈局(layout)

    • 之後對節點的大小、位置修改重新計算稱之為迴流
  • "重排/迴流"必然導致"重繪"

    • 比如改變一個網頁元素的位置,就會同時觸發"重排"和"重繪",因為佈局改變了
    • 所以迴流是一件很消耗效能的事情
  • 什麼屬性會導致迴流呢?

    • width top position.. (比如 DOM 結構發生改變 / 修改了佈局)

5.3 常見的觸發"迴流/重排"操作

  • Reflow 的成本比 Repaint 的成本高得多的多。
  • 所以在開發中要儘量避免發生迴流:

    1. 修改樣式時儘量一次性修改

      • 將多次改變樣式屬性的操作合併成一次操作
      • 預先定義好 class,然後修改 DOM 的 className
    2. 儘量避免頻繁的操作 DOM

      • 我們可以在一個 DocumentFragment 或者父元素中將要操作的 DOM 操作完成,再一次性的操作;
    3. 儘量避免透過 getComputedStyle 獲取尺寸、位置等資訊;
    4. 對某些元素使用 positionabsolute 或者 fixed

      • 並不是不會引起迴流,而是開銷相對較小,不會對其他元素造成影響。

5.4 合成和效能最佳化

  • 繪製的過程,可以將佈局後的元素繪製到多個合成圖層中

    • 這是瀏覽器的一種最佳化手段
  • 預設情況下,標準流中的內容都是被繪製在同一個圖層(Layer)中的
  • 而一些特殊的屬性,會建立一個新的合成層( CompositingLayer ),並且新的圖層可以利用 GPU 來加速繪製

    • 因為每個合成層都是單獨渲染
  • 那麼哪些屬性可以形成新的合成層呢? 常見的一些屬性:

    • 3D transforms
    • video、 canvas、 iframe
    • opacity 動畫轉換時
    • position: fixed
    • will-change:一個實驗性的屬性,提前告訴瀏覽器元素可能發生哪些變化
    • animation 或 transition 設定了 opacity、 transform
  • 分層確實可以提高效能,但是它以記憶體管理為代價,因此不應作為 web 效能最佳化策略的一部分過度使用

Pasted image 20221116223554

5.5 script 元素和頁面解析的關係

  • 我們現在已經知道了頁面的渲染過程,但是 JavaScript 在哪裡呢?

    • 事實上,瀏覽器在解析 HTML 的過程中,遇到了 script 元素是不能繼續構建 DOM 樹的;
    • 它會停止繼續構建,首先下載 JavaScript 程式碼,並且執行 JavaScript 的指令碼;
    • 只有等到 JavaScript 指令碼執行結束後,才會繼續解析 HTML,構建 DOM 樹;
  • 為什麼要這樣做呢?

    • 這是因為 JavaScript 的作用之一就是操作 DOM,並且可以修改 DOM
    • 如果我們等到 DOM 樹構建完成並且渲染再執行 JavaScript,會造成嚴重的迴流和重繪,影響頁面的效能;
    • 所以會在遇到 script 元素時,優先下載和執行 JavaScript 程式碼,再繼續構建 DOM 樹;
  • 但是這個也往往會帶來新的問題,特別是現代頁面開發中:

    • 在目前的開發模式中(比如 VueReact),指令碼往往比 HTML 頁面更“重”,處理時間需要更長;
    • 所以會造成頁面的解析阻塞,在指令碼下載、執行完成之前,使用者在介面上什麼都看不到;
  • 為了解決這個問題, script 元素給我們提供了兩個屬性(attribute): deferasync

6. defer 和 async 屬性

6.1 defer 屬性

  • defer 屬性:告訴瀏覽器不要等待指令碼下載,而繼續解析 HTML構建 DOM Tree

    • 指令碼會由瀏覽器來進行下載,但是不會阻塞 DOM Tree 的構建過程;
    • 如果指令碼提前下載好了,它會等待 DOM Tree 構建完成,在 DOMContentLoaded 事件之前先執行 defer 中的程式碼;
  • 所以 DOMContentLoaded 總是會等待 defer 中的程式碼先執行完成

Pasted image 20221118091923

  • 另外多個帶 defer 的指令碼是可以保持正確的順序執行的。

    • 從某種角度來說, defer 可以提高頁面的效能,並且推薦放到 head 元素中;
    • 注意: defer 僅適用於外部指令碼,對於 script 預設內容會被忽略。

6.2 async 屬性

  • sync 特性與 defer 有些類似,它也能夠讓指令碼不阻塞頁面
  • async 是讓一個指令碼完全獨立的:

    • 瀏覽器不會因 async 指令碼而阻塞(與 defer 類似);
    • async 指令碼不能保證順序,它是獨立下載、獨立執行,不會等待其他指令碼;
    • async 不會能保證在 DOMContentLoaded 之前或者之後執行

Pasted image 20221118092245

  • defer 通常用於需要在文件解析後操作 DOM 的 JavaScript 程式碼,並且對多個 script 檔案有順序要求的;

    • async 通常用於獨立的指令碼,對其他指令碼,甚至 DOM 沒有依賴的;