瀏覽器渲染簡述

singsong: 文本是本身看了一些不錯資料整理出來的,對該知識點感興趣的同窗能夠查看 參考文章小節。

✏️最新內容請以github上的爲準❗️css

爲何要寫這篇文章?

主要爲 CSS 優化工做打一下基礎。要編寫高性能的網站和應用,除了確保編寫的代碼儘量高效地運行外,還須要確保頁面性能,刷新頻率儘可能到達 60FPS。這就須要瞭解瀏覽器是如何進行渲染的。而瀏覽器渲染與 CSS 密切相關,所以只有瞭解其中工做原理才能讓 CSS 更好地工做。html

另外,接下來會出一篇優化實戰文章,會涉及 JavaScript 和 CSS 一些優化。其中關於 JavaScript 的優化以前已進行過介紹:常見的 JavaScript 內存泄露。本文是對 CSS 優化進行一個補充。html5

Contents

瀏覽器

  1. 用戶界面(User Interface):包括地址欄、前進/後退按鈕、書籤菜單等。除了瀏覽器主窗口顯示的您請求的頁面外,其餘顯示的各個部分都屬於用戶界面。
  2. 瀏覽器引擎(Browser engine):在用戶界面(User Interface)和渲染引擎(Rendering engine)之間傳送指令。
  3. 渲染引擎(Rendering engine):負責顯示請求的內容。若是請求的內容是 HTML,它就負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上。
  4. 網絡(Networking):用於網絡調用,好比 HTTP 請求。其接口與平臺無關,併爲全部平臺提供底層實現。
  5. JavaScript 解釋器(JavaScript Interperter):用於解析和執行 JavaScript 代碼。
  6. 用戶界面後端(UI Backend):用於繪製基本的窗口小部件,好比組合框和窗口。其公開了與平臺無關的通用接口,而在底層使用操做系統的用戶界面方法。
  7. 數據存儲(Data storage):這是持久層。瀏覽器須要在硬盤上保存各類數據,例如 Cookies。瀏覽器還支持諸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之類的存儲機制。

本文主要介紹瀏覽器的渲染,即渲染引擎(Rendering engine)負責的工做: 將請求的 HTML 和 CSS 內容解析並渲染在屏幕上。git

DOM tree

DOM:Document Object Model,文檔對象模型。它能夠以一種獨立於平臺和語言的方式,訪問和修改一個文檔的內容和結構。它定義了一組與平臺,語言無關的接口,該接口容許編程語言動態地訪問和修改結構化文檔。基於 DOM 表示的文檔被描述成一個樹形結構,使用 DOM 的接口能夠對 DOM 樹進行操做。github

這裏以一個實例展開講解,以下 HTML 結構包含一些文本和一張圖片:web

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

瀏覽器是如何處理這個 HTML 頁面:chrome

  1. 轉換(Conversion):瀏覽器從磁盤或網絡讀取 HTML 的字節碼,並根據文件的指定編碼(例如 UTF-8)將其轉換成對應的字符。
  2. 令牌化(Tokenizing):瀏覽器將字符串轉換成W3C HTML5 標準規定的各類 token,例如<html><body>,以及其餘尖括號內的字符串。每一個 token 都具備必定特殊含義和規則。
  3. 詞法分析(Lexing):將 token 轉換成定義其屬性和規則的「對象」。
  4. DOM tree:HTML 標籤訂義了不一樣標籤之間的關係(某些標籤包含在其餘標籤中),所建立的對象連接在樹狀數據結構中,該數據結構還捕獲原始標籤中定義的父——子關係:HTML 是 body 的父對象,body 是段落的父對象,依此類推。

整個過程的最終輸出是 HTML 頁面的 DOM tree,後續瀏覽器對頁面進一步的全部處理都會用到它。瀏覽器每次處理 HTML 標籤時,都會完成以上全部步驟:將字節轉換成字符,肯定 token,將 token 轉換成節點,而後構建 DOM tree編程

CSSOM tree

CSSOM:CSS Object Model,CSS 對象模型。CSSOM 定義了 JavaScript 訪問樣式的能力和方式。它是在 DOM 中的一些接口中,加入獲取和操做 CSS 屬性或接口的 JavaScript 接口,於是 JavaScript 能夠動態操做 CSS 樣式。DOM 提供了接口讓 JavaScript 修改 HTML 文檔,CSSOM 提供了接口讓 JavaScript 得到和修改 CSS 代碼設置的樣式信息。canvas

在瀏覽器構建 DOM 遇到 link 標籤時,該標籤引用一個外部 CSS 樣式表:style.css。因爲預見到須要利用該資源來渲染頁面,它會當即發出對該資源的請求,並返回如下內容:後端

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }

與處理 HTML 時相似,須要將收到的 CSS 規則轉換成某種瀏覽器可以理解和處理的內部表示。所以會重複 HTML 過程,不過是對 CSS 而不是 HTML:

將 CSS 字節轉換成字符,接着轉換成 token 和節點,最後將它們連接到 CSSOM tree 結構中:

cssom-tree.png

CSSOM tree 能夠用於肯定節點對象的計算樣式。如 span 標記包含了color:red樣式和繼承於 body 標記的font-size:16px樣式;

RenderObject tree(也稱爲 Render tree)

  • DOM tree 中,存在不可見與可見節點之分。顧名思義,不可見節點是不須要繪製最終頁面中的節點,如metaheadscript等,以及經過 CSS 樣式display:none隱藏的節點。相反可見節點是用戶可見的,如bodydivspancanvasimg等。對於這些可見節點,瀏覽器須要將它們的內容繪製到最終的頁面中,因此瀏覽器會爲它們創建對應的 RenderObject 對象。一個 RenderObject 對象保存了爲繪製 DOM 節點的各類信息。這些 RenderObject 對象與 DOM 對象相似,也構成一棵樹,稱爲RenderObject tree。RenderObject tree 是基於 DOM tree 創建起來的一棵新樹,是爲了佈局計算和渲染等機制而構建的一種新的內部表示。RenderObject tree 節點與 DOM tree 節點不是一一對應關係。由於建立一個 RenderObject 對象須要知足以下規則:
  • DOM tree 的 document 節點
  • DOM tree 中的可見節點,如htmlbodydiv等。而瀏覽器不會爲不可見節點建立 RenderObject 節點。
  • 某些狀況下瀏覽器須要建立匿名的 RenderObject 節點,該節點不對應 DOM 樹中的任何節點。

RenderObject 對象構成了 RenderObject tree,每一個 RenderObject 對象保存了爲繪製 DOM 節點的計算樣式。RenderObject tree 也能夠理解成由 CSSOM treeDOM tree 合併成:

Layout(佈局)

當瀏覽器建立 RenderObject 對象以後,每一個對象並不知道本身在設備視口內的位置、大小等信息。瀏覽器根據盒模型(Box-model)來計算它們的位置、大小等信息的過程稱爲佈局計算(重排)。佈局計算是一個遞歸的過程,這是由於一個節點的大小一般須要先計算它的孩子節點的位置、大小等信息。爲了計算節點在頁面中的確切大小和位置,瀏覽器會從 RenderObject tree 的根節點開始進行遍歷。

實例:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

頁面包含了兩個嵌套 div:父 div 將其的顯示尺寸設置爲 viewport 寬度的 50%,子 div 將其寬度設置爲其父項的 50%,即 viewport 寬度的 25%。

RenderLayer tree

瀏覽器渲染引擎並非直接使用 RenderObject tree 進行繪製,爲了方便處理 Positioning(定位),Clipping(裁剪),Overflow-scroll(頁內滾動),CSS Transform/Opacity/Animation/Filter,Mask or Reflection,Z-indexing(Z 排序)等,瀏覽器須要會爲一些特定的 RenderObject 生成對應的 RenderLayer,並生成一棵對應的 RenderLayer tree。而這些特定的 RenderObject 跟對應的 RenderLayer 就是直屬的關係,若是它們的子節點若是沒有對應的 RenderLayer,就從屬於父節點的 RenderLayer。最終,每個 RenderObject 都會直接或者間接地從屬於一個 RenderLayer。所以 RenderObject 節點與 RenderLayer 節點不是一一對應關係,而是一對多的關係。那須要知足什麼條件,渲染引擎才爲 RenderObject 創建對應的 RenderLayer:

  • It's the root object for the page
  • It has explicit CSS position properties (relative, absolute or a transform)
  • It is transparent
  • Has overflow, an alpha mask or reflection
  • Has a CSS filter
  • Corresponds to <canvas> element that has a 3D (WebGL) context or an accelerated 2D context
  • Corresponds to a <video> element

翻譯:

  • DOM tree 的 Document 節點對應的 RenderObject 節點和 HTML 節點對應的 RenderObject 節點
  • 顯式指定 CSS position 屬性的 RenderObject 節點
  • 有透明度的 RenderObject 節點
  • 有 overflow,alpha 和 reflection 的樣式 RenderObject 節點
  • 有 filter 樣式的 RenderObject 節點
  • 使用 Canvas 2D 和 3D(WebGL)技術的 RenderObject 節點
  • video 元素對應的 RenderObject 節點

每一個 RenderLayer 對象能夠想象成圖像中一個圖層,各個圖層疊加構成了一個圖像。瀏覽器會遍歷 RenderLayer tree,再遍歷從屬這個 RenderLayer 的 RenderObject,RenderObject 對象存儲有繪製信息,並進行繪製。RenderLayer 和 RenderObject 共同決定了最終呈現的網頁內容,RenderLayer tree 決定了網頁的繪製的層次順序,而從屬於 RenderLayer 的 RenderObject 決定了該 RenderLayer 的內容。

Rendering(渲染方式)

在完成構建 RenderLayer tree 以後,瀏覽器會使用圖形庫將其構建的渲染模型繪製出來,該過程分爲兩個階段:

  • 繪製:將從屬每一個 RenderLayer 圖層上的 RenderObject 繪製在其 RenderLayer 上。即繪製(Paint)或者光柵化(Rasterization),將一些繪圖指令轉換成真正的像素顏色值。

    • 軟件繪圖:CPU 來完成繪圖操做
    • 硬件加速繪圖:GPU 來完成繪圖操做
  • 合成(compositing):將各個 RenderLayer 圖層合併成到一個位圖(Bitmap)中。同時還可能包括位移(Translation),縮放(Scale),旋轉(Rotation),Alpha 合成等操做。

渲染引擎的渲染,目前有三種網頁的渲染方式:

  • 硬件加速合成(Accelerated Compositing):使用 GPU 來完成合成工做。
  • 合成化渲染:使用合成(compositing)技術的渲染稱。
  • 軟件渲染方式:使用 CPU 來繪製每一個 RenderLayer 圖層的內容(RenderObject)到一個位圖,即一塊 CPU 使用的內存空間。繪製每一層的時候都會使用該位圖,區別在於繪製的位置可能不同,繪製順序按照從後到前。所以軟件渲染機制是沒有合成階段的。
  • 硬件加速渲染的合成化渲染方式:使用 GPU 來繪製全部合成層,並使用 GPU 硬件來加速合成。
  • 軟件繪圖的合成化渲染方式: 某些合成層使用 CPU 來繪圖,另一些使用 GPU 來繪製。對於使用 CPU 來繪製的圖層,該層的繪製結果會先保存在 CPU 內存中,以後會被傳輸到 GPU 內存中,而後再使用 GPU 來完成合成工做。

第二種和第三種渲染方式,都是使用了合成化渲染技術,合成工做也都是由 GPU 來作。對於常見的 2D 繪圖操做,使用 GPU 來繪圖不必定比使用 CPU 繪圖在性能上有優點,例如繪製文字、點、線等。緣由是 CPU 的使用緩衝機制有效減小了重複繪製的開銷並且不須要考慮與 GPU 並行。另外,GPU 的內存資源相對 CPU 的內存資源來講比較緊張,並且網頁的分層使得 GPU 的內存使用相對比較多。鑑於此,就目前的狀況來看,三者都存在是有其合理性的,下面分析一下它們的特色:

  • 軟件渲染是目前很常見的技術,也是瀏覽器最先使用的渲染方式。這一技術比較節省內存,特別是寶貴的 GPU 內存,可是軟件渲染只能處理 2D 方面的操做。簡單的網頁沒有複雜繪圖或者多媒體方面的需求,軟件渲染方式就比較合適來渲染該類型的網頁。問題是,一旦趕上了 HTML5 的不少新技術,軟件渲染顯得無能爲力,一是由於能力不足;二是由於性能很差,例如視頻、Canvas 2D 等。因此,軟件渲染技術被用的愈來愈少,特別是在移動領域。軟件渲染同硬件加速渲染另一個很不一樣的地方就是對更新區域的處理。當網頁中有一個更新小型區域的請求(如動畫)時,軟件渲染可能只須要計算一個極小的區域,而硬件渲染可能須要從新繪製其中的一層或者多層,而後再合成這些層。硬件渲染的代價可能會大得多。
  • 對於硬件加速的合成化渲染方式來講,每一個層的繪製和全部層的合成均使用 GPU 硬件來完成,這對須要使用 3D 繪圖的操做來講特別合適。這種方式下,在 RenderLayer 樹以後,瀏覽器還須要創建更多的內部表示,目的是支持硬件加速機制,這顯然會消耗更多的內存資源。可是,一方面,硬件加速機制可以支持如今全部的 HTML5 定義的 2D 或者 3D 繪圖標準;另外一方面,關於更新區域的討論,若是須要更新某個層的一個區域,由於軟件渲染沒有爲每一層提供後端存儲,於是它須要將和這個區域有重疊部分的全部層次的相關區域一次從後往前從新繪製一遍,而硬件加速渲染只須要從新繪製更新發生的層次,於是在某些狀況下,軟件渲染的代價又變得更大。固然,這取決於網頁的結構和渲染策略。
  • 軟件繪圖的合成化渲染方式結合了前面兩種方式的優勢,這時由於不少網頁可能既包含基本的 HTML 元素,也包含一些 HTML5 新功能,使用 CPU 繪圖方式來繪製某些層,使用 GPU 來繪製其餘一些層。緣由固然是前面所述的基於性能和內存方面綜合考慮的結果。
瀏覽器還可使用多線程的渲染架構,將網頁內容繪製到後端存儲的操做放到另一個獨立的線程(繪製線程),而原來線程轉爲合成線程,繪製線程跟合成線程之間可使用同步,部分同步,徹底異步等做業模式,讓瀏覽器能夠在性能與效果之間根據須要進行選擇。

GrphicsLayer tree

對於軟件渲染而言,到 RenderLayer tree 就結束了,後面不會創建其它額外的樹來對應於 RenderLayer tree。可是,對於硬件渲染來講,在 RenderLayer tree 以後,瀏覽器渲染引擎爲硬件渲染提供了更多的內部結構來支持這個機制。

在硬件加速渲染的合成化渲染和軟件繪圖的合成化渲染架構下,一個 RenderLayer 對象若是須要後端存儲,它會建立一個 RenderLayerBacking 對象,該對象負責 Renderlayer 對象所須要的各類存儲。理想狀況下,每一個 RenderLayer 均可以建立本身的後端存儲,事實上不是全部 RenderLayer 都有本身的 RenderLayerBacking 對象。若是一個 RenderLayer 對象被像樣的建立後端存儲,那麼將該 RenderLayer 稱爲合成層(Compositing Layer)。

哪些 RenderLayer 對象能夠是合成層?若是一個 RenderLayer 對象具備如下的特徵之一,那麼它就是合成層:

  • Layer has 3D or perspective transform CSS properties
  • Layer is used by < video> element using accelerated video decoding
  • Layer is used by a < canvas> element with a 3D context or accelerated 2D context
  • Layer is used for a composited plugin
  • Layer uses a CSS animation for its opacity or uses an animated webkit transform
  • Layer uses accelerated CSS filters
  • Layer with a composited descendant has information that needs to be in the composited layer tree, such as a clip or reflection
  • Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer is rendered on top of a composited layer)

翻譯:

  • RenderLayer 具備 3D 或透視轉換的 CSS 屬性
  • RenderLayer 包含使用硬件加速的視頻解碼技術的<video>元素
  • RenderLayer 包含使用硬件加速的 2D 或 WebGL-3D 技術的<canvas>元素
  • RenderLayer 使用了合成插件。
  • RenderLayer 使用了opacitytransform動畫
  • RenderLayer 使用了硬件加速的 CSS Filters 技術
  • RenderLayer 後代中包含了一個合成層(若有 clip 或 reflection 屬性)
  • RenderLayer 有一個 z-index 比本身小的合成層(即在一個合成層之上)

每一個合成層都有一個 RenderLayerBacking,RenderLayerBacking 負責管理 RenderLayer 所須要的全部後端存儲,由於後端存儲可能須要多個存儲空間。在瀏覽器(WebKit)中,存儲空間使用類 GraphicsLayer 來表示。瀏覽器會爲這些 RenderLayer 建立對應的 GraphicsLayer,不一樣的瀏覽器須要提供本身的 GrphicsLayer 實現用於管理存儲空間的分配,釋放,更新等等。擁有 GrphicsLayer 的 RenderLayer 會被繪製到本身的後端存儲,而沒有 GrphicsLayer 的 RenderLayer 它們會向上追溯有 GrphicsLayer 的父/祖先 RenderLayer,直到 Root RenderLayer 爲止,而後繪製在有 GrphicsLayer 的父/祖先 RenderLayer 的存儲空間,而 Root RenderLayer 老是會建立一個 GrphicsLayer 並擁有本身獨立的存儲空間。在將每一個合成圖層包含的 RenderLayer 內容繪製在合成層的後端存儲中,這裏繪製能夠是軟件繪製或硬件繪製。接着由合成器(Compositor)將多個合成層合成起來,造成最終用戶可見的網頁,實際上就是一張圖片。

GraphicsLayer 又構成了一棵與 RenderLayer 並行的樹,而 RenderLayer 與 GraphicsLayer 的關係有些相似於 RenderObject 與 RenderLayer 之間的關係。以下是 DOM treeRenderObject treeRenderLayer treeGraphicsLayer tree關係圖:

這樣能夠合併一些 RenderLayer 層,從而減小內存的消耗。其次,合併以後,減小了合併帶來的重繪性能和處理上的困難。在硬件加速渲染的合成化渲染和軟件繪圖的合成化渲染架構下,RenderLayer 的內容變化,只須要更新所屬的 GraphicsLayer 的緩存便可,而緩存的更新,也只須要繪製直接或者間接屬於這個 GraphicsLayer 的 RenderLayer,而不是全部的 RenderLayer。特別是一些特定的 CSS 樣式屬性的變化,實際上並不引發內容的變化,只須要改變一些 GraphicsLayer 的混合參數,而後從新混合便可,而混合相對繪製而言是很快的,這些特定的 CSS 樣式屬性咱們通常稱之爲是被加速的,不一樣的瀏覽器支持的情況不太同樣,但基本上 CSS Transform & Opacity 在全部支持混合加速的瀏覽器上都是被加速的。被加速的 CSS 樣式屬性的動畫,就比較容易達到 60 幀/每秒的流暢效果了。

不過並非擁有獨立緩存的 RenderLayer 越多越好,太多擁有獨立緩存的 RenderLayer 會帶來一些嚴重的反作用:

  • 它大大增長了內存的開銷,這點在移動設備上的影響更大,甚至致使瀏覽器在一些內存較少的移動設備上沒法很好地支持圖層合成加速;
  • 它加大了合成的時間開銷,致使合成性能的降低,而合成性能跟網頁滾動/縮放操做的流暢度又息息相關,最終致使網頁滾動/縮放的流暢度降低,讓用戶以爲操做不夠流暢。

Tiled Rendering(瓦片渲染)

一般一個合成層的後端存儲被分割成多個大小相同的瓦片狀的小存儲空間,每一個瓦片能夠理解爲 OpenGL 中的一個紋理,合成層的結果被分開存儲在這些瓦片中。爲何使用瓦片化的後端存儲?

  • DOM 樹種的 html 元素所在的層可能會比較大,由於網頁的高度很大,若是隻是使用一個後端存儲的話,那麼須要一個很大的紋理對象,可是實際的 GPU 硬件可能只支持很是有限的紋理大小。
  • 在一個比較大的合成層中,可能只是其中一部分發生變化,根據以前的介紹,須要從新繪製整個層,這樣必然產生額外的開銷,使用瓦片話的後端存儲,就只須要重繪一些存在更新的瓦片。
  • 當層發生滾動的時候,一些瓦片可能再也不須要,而後渲染引擎須要一些新的瓦片來繪製新的區域,這些大小相同的後端存儲很容易重複利用。

High Performance Animations(流暢動畫)

網頁加載後,繪製新的每一幀,通常都須要通過計算佈局(layout)、繪圖(paint)、合成(composite)三階段。所以要想提升頁面性能(或 FPS),須要減小每一幀的時間。而在這三個階段中,layout 和 paint 比較耗時間,而合成須要的時間相對較少一些。

layout

若是修改 DOM 元素的 layout 樣式(如 width, heihgt 等),瀏覽器會計算頁面須要 relayout 的元素,而後觸發一個 relayout。被 relayout 的元素,接着會執行繪製,最後進行渲染合併生成頁面。

paint

若是修改 DOM 元素的 paint 樣式(如 color, background 等),瀏覽器會跳過佈局,直接執行繪製,再進行合成。

composite

若是修改 DOM 元素的 composite 樣式(如 transform, opacity 等)。瀏覽器會跳過佈局和繪製,直接執行合成。該過程是開銷最小的,也是優化着手點。

若是想知道修改任何指定 CSS 樣式會觸發 layout、paint、composite 中的哪個,請查看CSS 觸發器

優化

能夠經過什麼途徑進行優化,減小每一幀的時間(避免過多 layout 或 paint):

  • 使用適合的網頁分層技術減小 layout 和 paint。一旦有請求更新,若是沒有分層,渲染引擎可能須要從新繪製全部區域,由於計算更新部分對 GPU 來講可能消耗更多的時間。而網頁分層以後,部分區域的更新可能只在網頁的一層或幾層,而不須要將整個網頁從新繪製。經過從新繪製網頁的一層或幾層,並將它們和其餘以前繪製完的層合併起來,既能使用 GPU 的能力,又可以減小重繪的開銷。
  • 使用合成屬性樣式(opcity、tansform)來完成 tansition 或 animation。當合成器合成時候,每一個合成層均可以設置變形屬性:位移(Translate)、縮放(Scale)、旋轉(Rotation)、opacity,這些屬性僅僅改變合成層的變換參數,而不須要 layout 和 paint 操做,極大地減小每一幀的渲染時間。

即便用 GPU 硬件加速,爲某些 RenderLayer 建立對應 GraphicsLayer。經過爲每個合成層設置transform屬性來完成 transition 或 animation,有效地避免 relayout 和 repaint 的開銷。

總結

本文重點介紹了瀏覽器渲染引擎的渲染過程,涉及了 DOM treeCSSOM treeRenderObject treeRenderLayer treeGraphicsLayer tree。並對各類渲染模式進行了簡單介紹,其中引入了硬件加速機制,還給出一些優化建議。瞭解這些知識點對咱們開發高性能的 web 應用會有很大的幫助。

參考文章:

相關文章
相關標籤/搜索