瀏覽器的關鍵渲染路徑深入解析

lx7575000發表於2017-03-08

在in面試被CTO問到的問題,謹以此翻譯來表達對自己知識匱乏的鄙視。

當瀏覽器接收到從伺服器傳送過來的HTML頁面資訊,在將其繪畫渲染到螢幕上之前會有許多的步驟要做。瀏覽器繪製頁面需要做的這一系列行為我們稱為關鍵渲染路徑。

瞭解CRP的知識對於你理解如何提升網站渲染效率非常有用,CRP總共有六步:

  1. 構建DOM樹
  2. 構建CSSOM樹
  3. 執行JavaScript
  4. 建立渲染樹
  5. 生成佈局
  6. 繪製頁面

1. 構造DOM樹

DOM(Document Object Model)樹是一個表示全解析過的HTML頁面的物件。從根節點元素<html>開始,會逐個建立頁面中的每個元素/文字節點。元素包裹的其他元素會被作為子元素節點,並且每個節點會包含其全部的屬性。例如:<a>標籤會有href屬性與其節點關聯。 舉個例子:

<html>  
<head>  
  <title>Understanding the Critical Rendering Path</title>
  <link rel="stylesheet" href="style.css">
</head>  
<body>  
  <header>
      <h1>Understanding the Critical Rendering Path</h1>
  </header>
  <main>
      <h2>Introduction</h2>
      <p>Lorem ipsum dolor sit amet</p>
  </main>
  <footer>
      <small>Copyright 2017</small>
  </footer>
</body>  
</html>

上述HTML會被解析成如下的DOM樹

HTML很好的特點在於不需要全部載入完成頁面所有內容才來顯示網頁,可以解析完成一部分內容呈現一部分。但是,其他資源比如CSS和JavaScript會阻止頁面的渲染。

2. 構建CSSOM樹

CSSOM(CSS Object Model)是一個表示各個DOM相關樣式的物件,它的表示方法與DOM相似,但是各個節點存在相關的樣式值。不論其是顯示、還是隱式宣告這些樣式。 在style.css檔案中,我們有如下樣式:

body { font-size: 18px; }

header { color: plum; }  
h1 { font-size: 28px; }

main { color: firebrick; }  
h2 { font-size: 20px; }

footer { display: none; }

接著會建立如下的CSSOM樹:

CSS一直被認為是一種渲染阻塞資源。因此如果在首次載入時沒有全部解析資源內容就無法進行渲染樹的構建。與HTML不同,CSS具有層疊繼承的特性,因此不能進行區域性載入。定義在文件後面的樣式屬性會覆蓋或更改寫之前定義的同類屬性。即,如果如果CSS可以進行區域性載入的話會導致出現載入錯樣式的情況。因此表明,CSS必須全部解析之後才能進行下一步。

如果CSS檔案適用於當前裝置的話,僅僅只是會阻塞渲染。<link rel="stylesheet">標籤可以接受media屬性來指定特定樣式寬度的特定媒體查詢。例如:如果我們有一個樣式表具有orientation:landscape的媒體屬性,並且我們檢視該頁面使用portrait模式,就不會出現資源載入而產生的渲染阻塞情況。

CSS要會導致指令碼阻塞,這是由於JavaScript檔案必須等待CSSOM構建結束之後才進行載入。

執行JavaScript

JavaScript被認為是解析阻塞資源,這表示當解析HTML文件自身時候會被JavaScript給阻塞掉。

當解析器解析到<script>標籤時,無論該資源是內部還是外鏈的都會停止解析,並且等到資源被下載並執行結束後才繼續進行解析。這也是為什麼如果我們有一個引用了JavaScript檔案的元素,它必須被放在可視文件元素之外的原因。

為避免JavaScript解析阻塞,它可以通過設定async屬性來要求其非同步載入。

<script async src="script.js">

4 建立渲染樹(Render Tree)

渲染樹是DOM樹和CSSOM樹的結合體,它代表最終會渲染在頁面上的元素的結構物件。它表示只會關注可見內容,對於被隱藏或者CSS屬性display:none的屬性,不會被包含在結構內。

使用上面例子的DOM和CSSOM,渲染樹被建立如下:

5. 生成佈局

佈局決定了瀏覽器視窗大小,它提供了上下文依賴的CSS樣式,如百分比或視窗的單位。視窗尺寸通常通過<head>標籤中的<meta>中的viewport設定來決定。如果不存在該標籤,則通常預設為980px

例如:最常用的meta viewport值是設定視窗大小與裝置寬度對應:

<meta name="viewport" content="width=device-width,initial-scale=1">

舉個例子,如果使用者使用裝置訪問網頁,寬度為1000px。然後整體視窗尺寸就會基於這個寬度值了,比如50%就是500px,10vw就是100px。

6. 繪製頁面

最後,在繪製頁面步驟。頁面上的所有可見內容都會被轉換為畫素並呈現在螢幕上。

具體的繪製時間跟DOM數以及應用的樣式有關。有些樣式會話費更多的執行時間,比如複雜的漸變背景圖片所需要的計算時間遠超過簡單固定背景色。

整合所有

想要看到關鍵渲染路徑的執行流程,可以使用Chrome的DevTools:

<html>  
<head>  
  <title>Understanding the Critical Rendering Path</title>
  <link rel="stylesheet" href="style.css">
</head>  
<body>  
  <header>
      <h1>Understanding the Critical Rendering Path</h1>
  </header>
  <main>
      <h2>Introduction</h2>
      <p>Lorem ipsum dolor sit amet</p>
  </main>
  <footer>
      <small>Copyright 2017</small>
  </footer>
  <script src="main.js"></script>
</body>  
</html>

可以看關於頁面載入時的事件日誌,以下是我們獲得的:

1、傳送請求:傳送GET請求index.html

2、解析HTML然後傳送請求:開始解析HTML並構建DOM,然後傳送GET請求style.css和main.js。

3、解析樣式表:根據style.css生成CSSOM

4、執行計算指令碼:執行main.js

5、佈局:基於HTML的元視窗標籤,生成佈局
繪製頁面:繪製網頁

相關文章