【譯】理解關鍵渲染路徑

關於本文:

原文地址翻譯地址 譯者:野草

本文發表於前端早讀課【第875期】

有一個很經典的面試題:當你在瀏覽器輸入一個網址並按下回車之後發生了什麼?今天我們就來說說當瀏覽器從伺服器獲取了HTML文件之後經歷了什麼。事實上,從獲取HTML文件直到瀏覽器以像素點的方式在屏幕中繪製出頁面的內容確實經歷了很多步驟,這些步驟我們稱之為關鍵渲染路徑(Critial Rendering Path)

理解關鍵渲染路徑是提高頁面性能的關鍵所在。總體來說,關鍵渲染路徑分為六步。

  • 創建DOM樹(Constructing the DOM Tree)
  • 創建CSSOM樹(Constructing the CSSOM Tree)
  • 執行腳本(Running JavaScript)
  • 生成渲染樹(Creating the Render Tree)
  • 生成布局(Generating the Layout)
  • 繪製(Painting)

創建DOM樹

DOM(文檔對象模型)樹是HTML頁面完全解析後的一種表示方式。從根元素<html>開始,頁面上每個元素或者文本都會創建一個對應的節點。每個節點都包含了這個元素的所有屬性,並且嵌套在元素內的元素會被解析成外層元素對應的節點的子節點。比如,元素<a>標籤對應的節點會有一個屬性為href對應的子節點。

我們來看下面這個簡單的HTML結構:

<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>

它將會被解析生成以下的DOM樹。

幸運的是,HTML可以部分執行並顯示,也就是說,瀏覽器並不需要等待整個HTML全部解析完畢才開始顯示頁面。但是,其他的資源有可能阻塞頁面的渲染,比如CSS,JavaScript等。

創建CSSOM樹

CSSOM(CSS對象模型)樹是對附在DOM結構上的樣式的一種表示方式。它與DOM樹的呈現方式相似,只是每個節點都帶上樣式 ,包括明確定義的和隱式繼承的。

在上述的HTML頁面中,style.css文件代碼如下:

body { font-size: 18px; }header { color: plum; } h1 { font-size: 28px; }main { color: firebrick; } h2 { font-size: 20px; }

由此會生成以下的CSSOM樹。

CSS是一種渲染阻塞資源(render blocking resource),它需要完全被解析完畢之後才能進入生成渲染樹的環節。CSS並不像HTML那樣能執行部分並顯示,因為CSS具有繼承屬性, 後面定義的樣式會覆蓋或者修改前面的樣式。如果我們只使用樣式表中部分解析好的樣式,我們可能會得到錯誤的頁面效果。所以,我們只能等待CSS完全解析之後,才能進入關鍵渲染路徑的下一環節。

需要注意的是,只有CSS文件適用於當前設備的時候,才能造成渲染阻塞。標籤<link rel=」stylesheet」>接受media屬性,該屬性規定了此處的CSS文件適用於哪種設備。如果我們有個設備屬性值為orientation: landscape(橫向)的樣式,當我們豎著瀏覽頁面的時候,這個CSS資源是不會起作用的,也就不會阻塞渲染的過程了。

因為JavaScript腳本的執行必須等到CSSOM生成之後,所以說CSS也會阻塞腳本(script blocking)

執行JavaScript

JavaScript是一種解析阻塞資源(parser blocking resource),它能阻塞HTML頁面的解析。

當頁面解析到<script>標籤,不管腳本是內聯的還是外聯,頁面解析都會暫停,轉而載入JavaScript文件(外聯的話)並且執行JavaScript。這也是為什麼如果JavaScript文件有引用HTML文檔中的元素,JavaScript文件必須放在那個元素的後面。

為了避免JavaScript文件阻塞頁面的解析,我們可以在<script>標籤上添加async屬性,使得JavaScript文件非同步載入。

<script async src="script.js">

生成渲染樹

渲染樹是DOM和CSSOM的結合,是最終能渲染到頁面的元素的樹形結構表示。也就是說,它包含能在頁面中最終呈現的元素,而不包含那些用CSS樣式隱藏的元素,比如帶有display: none;屬性的元素。

所以,上述例子的渲染樹如下所示。

生成布局

布局決定了視口的大小,為CSS樣式提供了依據,比如百分比的換算或者視口的總像素值。視口大小是由meta標籤的name屬性為viewport的內容設置所決定的,如果缺少這個標籤,默認的視口大小為980px。

最常見的viewport設置是自適應於設備尺寸,設置如下:

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

假設用戶訪問一個顯示在設備寬度為1000px的頁面,一半的視口大小就是500px,10%就是100px,以此類推。

繪製

最後,頁面上可見的內容就會轉化為屏幕上的像素點。

繪製過程所需要花費的時間取決於DOM的大小以及元素的CSS樣式。有些樣式比較耗時,比如一個複雜的漸變背景色比起簡單的單色背景需要更多的時間來渲染。

總結

我們可以利用Chrome開發者工具下的Timeline去觀察整個關鍵路徑渲染的過程。

我們還是拿上面這個最簡單的HTML例子(添加了<script>標籤)

<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>

我們查看頁面載入過程的Event log選項,我們就得到以下的結果:

  • 發送請求(Send Request) —— 發送GET請求獲取index.html
  • 解析HTML(Parse HTML),再次發送請求 —— 開始解析HTML文件,創建DOM結構,發送請求獲取style.css和main.js
  • 解析樣式文件(Parse Stylesheet) —— 根據style.css生成CSSOM樹
  • 執行腳本(Evaluate Script) —— 執行main.js
  • 生成布局(Layout) —— 基於HTML頁面中的meta viewport標籤生成布局
  • 繪製(Paint) —— 在瀏覽器頁面繪製像素點

推薦閱讀:

node.js-腳本合併
[譯]Web 的現狀:網頁性能提升指南
小記JS模塊化
[譯] HTTP/2 Server Push 詳解
Vue.js前後端同構方案之準備篇——代碼優化

TAG:HTMLCSS | HTML解析 | 前端性能優化 |