理解關鍵的渲染路徑
當瀏覽器從伺服器接收到一個HTML頁面的請求時,到螢幕上渲染出來要經過很多個步驟。瀏覽器完成這一系列的執行,或者說渲染出來我們常常稱之為“關鍵渲染路徑”(Critical Rendering Path)。
理解CRP(Critical Rendering Path)相關的知識可以更好的提高網站的效能。那麼理解我們從下面六個部分來理解CRP相關的知識:
- 列表內容
- 列表內容
- 構建DOM樹
- 樹建CSSOM樹
- 執行JavaScript
- 建立Render樹
- 生成佈局
- 繪製(Painting)
構建DOM樹
DOM(文件物件模型)樹是一個完全解析的HTML頁面物件。從<html>
<a>
元素,它就有與之相關的href
節點。
來看下面這個簡單的DOM示例:
<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比較好的是它可以執行部分。完整的文件不需要載入的內容會在頁面的開始呈現。然而,比如CSS和JavaScript可以阻止頁面的呈現。
構建CSSOM樹
CSSOM(CSS物件模型)是一個表示DOM樣式的物件。它類似於DOM,但是是每個節點相關的樣式,包括他們是否顯式宣告或隱式繼承。
比如,在style.css
檔案中對上面的DOM有這樣的一些樣式:
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必須充分解析才能繼續下一個階段。
如果只運用於當前裝置,CSS檔案只被認為阻塞。<link rel="stylesheet">
標籤可以接受一個media
屬性,可以用來指定樣式適用於何種媒體。例如,我們有一個樣式表,它的media
屬性設定為orientation:landscape
時,如果在portrait
模式下檢視頁面,那麼這個樣式表不會被認為是一個阻塞資源。
CSS還會阻塞JavaScript。那是因為JavaScript檔案必須要等CSSOM構建完才會執行。
執行JavaScript
JavaScript被認為是一個“解析器阻塞資源”。這意味著解析的HTML文件本身就會被JavaScript阻塞。
當解析器讀到<script>
標記,不管是內部的還是外部的,它停止獲取(如果是外部的)並執行它。這個為什麼,如果我們有一個JavaScript檔案,該檔案引用文件中的元素,那麼它必須放在這個元素的後面。
為了避免JavaScript解析器造成阻塞,可以新增async
屬性,非同步載入它。
<script async src="script.js">
建立Render樹
Render樹是DOM和CSSOM的組合。這個樹代表最終在頁面上呈現的東西。這意味著它只抓住了可見的內容,將不包括設定了hidden
的元素和CSS設定了display:none
的元素。
使用上面的DOM和CSSOM,構建的Render樹如下圖所示:
生成佈局
佈局根據CSS樣式設定的大小來決定頁面在視窗中的尺寸。視窗的大小是由<head>
中viewport
標記來決定,如果沒有提供這個標記,預設使用的視窗寬度是980px
。
例如,常見的設定視窗的meta
標籤,指定的大小對應裝置寬度:
<meta name="viewport" content="width=device-width,initial-scale=1">
如果使用者訪問頁面寬度是基於裝置的寬度1000px
。那一半的視窗就是500px
,10vw
就是100px
。
繪製(Painting)
最後就是繪製(Painting)步驟,把頁面可見的內容轉化為畫素在螢幕上顯示。
繪製需要多少時間這取決於DOM的大小以及應用的樣式。有一些樣式需要更多的執行時間。例如,一個複雜的漸變背景影象比一個單一顏色背景繪製需要更多的時間。
把它們結合起來
我們可以在DevTools中看到關鍵渲染路徑的過程。在Chrome瀏覽器中,點選Timeline選項。
拿文章前面的示例(這裡添加了<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>
如果我們看頁面的載入日誌,將看到如下這樣的資料:
- 傳送請求:傳送GET,請求
index.html
- 解析HTML併發送請求:解析HTML並且構建DOM樹,傳送GET請求,請求
style.css
和main.js
- 解析樣式:根據
style.css
建立CSSOM - 指令碼評估:
main.js
評估 - 佈局:基於HTML中的視窗meta生成佈局
- 繪製:繪製頁面
基於這些資訊,我們可以決定如何優化關鍵渲染路徑。我也將在後續的文章中深入的介紹這方面的知識。