當(dāng)一個瀏覽器接收到從服務(wù)器發(fā)來的html頁面,在渲染并呈現(xiàn)到屏幕上之前,有很多步驟要做。瀏覽器渲染頁面需要做的一系列行為被稱作“關(guān)鍵渲染路徑(Critical Rendering Path 簡稱CRP)”。
CRP
的知識對于如何提升網(wǎng)站性能是相當(dāng)有用的。CRP
有6個步驟:
- 構(gòu)建DOM樹
- 構(gòu)建CSSOM樹
- 運(yùn)行JavaScript
- 創(chuàng)建渲染樹
- 生成布局
-
繪制頁面
CRP的6個步驟
構(gòu)建DOM樹
DOM(Document Object Model)樹是一個表示整個解析過的HTML頁面的對象,從根節(jié)點(diǎn)<html>
開始,會創(chuàng)建頁面中的每個 元素/文本 節(jié)點(diǎn)。嵌套在其他元素中的元素作為字節(jié)點(diǎn),每個節(jié)點(diǎn)都包含了其所有的元素屬性,例如: 一個<a>
節(jié)點(diǎn)將有 href
屬性與其關(guān)聯(lián)。
舉個例子
<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的優(yōu)點(diǎn)在于它不必等待整個頁面加載完成才呈現(xiàn)頁面,可以解析一部分,顯示一部分。但是像CSS、JavaScript等其他資源會阻止頁面渲染。
構(gòu)建CSSOM樹
CSSOM(CSS Object Model) 是一個跟DOM相關(guān)的樣式對象。它跟DOM的表示方法是相似的,但是不論顯式聲明還是隱式繼承,每個節(jié)點(diǎn)都存在關(guān)聯(lián)樣式。
In the style.css file from the document mentioned above, we have the folowing styles
在上面提到的html頁面的style.css
中的樣式如下
body { font-size: 18px; }
header { color: plum; }
h1 { font-size: 28px; }
main { color: firebrick; }
h2 { font-size: 20px; }
footer { display: none; }
它會被構(gòu)建成下面的CSSOM樹
CSS 被認(rèn)為是 “渲染阻塞資源”,它意味著如果不首先完全解析資源,渲染樹是無法構(gòu)建的。CSS由于它的層疊繼承的性質(zhì),不能像HTML一樣解析一部分,顯示一部分。定義在文檔后面的樣式會覆蓋或改寫之前定義的樣式,因?yàn)樵谡麄€樣式表都被解析之前,如果我們使用了在樣式表中較早定義的樣式,那錯誤的樣式將被應(yīng)用。這意味著CSS必須被全部解析之后,才能開始下一步。
如果CSS文件適用于當(dāng)前設(shè)備的話,僅僅只是會阻塞渲染。<link rel="stylesheet">
標(biāo)簽可以使用media
屬性,用來指定特定樣式寬度的特定媒體查詢。
舉個例子,如果我們有一個包含媒體屬性orientation:landscape
的樣式,我們使用縱向模式(portrait mode)查看頁面,這個資源將不會阻塞渲染。
CSS 也會導(dǎo)致腳本阻塞。這是因?yàn)镴avaScript文件必須等待CSSOM被構(gòu)建后才能運(yùn)行。
運(yùn)行JavaScript
JavaScript被認(rèn)為是解析阻塞資源
,這意味著HTML的解析會被JavaScript阻塞。
當(dāng)解析器解析到 <script>
標(biāo)簽時(shí),無論該資源是內(nèi)部還是外鏈的都會停止解析,先去下載資源。這也是為什么,當(dāng)頁面內(nèi)有引用JavaScript文件時(shí),引用標(biāo)簽要放到可視元素之后了。
為避免JavaScript解析阻塞,它可以通過設(shè)定 async 屬性來要求其異步加載。
<script async src="script.js">
創(chuàng)建渲染樹
渲染樹是DOM和CSSOM的結(jié)合體,它代表最終會渲染在頁面上的元素的結(jié)構(gòu)對象。這意味著它只關(guān)注可見內(nèi)容,對于被隱藏或者CSS屬性 display:none 的屬性,不會被包含在結(jié)構(gòu)內(nèi)。
使用上面例子的DOM和CSSOM,渲染樹被創(chuàng)建如下:
生成布局
布局決定了瀏覽器視窗的大小,它提供了上下文依賴的CSS樣式,如百分比或窗口的單位。視窗尺寸通常通過 <head>
標(biāo)簽中的 <meta>
中的 viewport 設(shè)定來決定。如果不存在該標(biāo)簽,則通常默認(rèn)為 980px
例如,最常用的 meta veiwport
的值將會被設(shè)置為和設(shè)備寬度相符:
<meta name="viewport" content="width=device-width,initial-scale=1">
如果用戶訪問網(wǎng)頁的設(shè)備寬度為1000px。然后整體視窗尺寸就會基于這個寬度值了,比如 50% 就是500px, 10vw 就是100px 等等。
繪制頁面
最后,在繪制頁面步驟。頁面上的所有可見內(nèi)容都會被轉(zhuǎn)換為像素并呈現(xiàn)在屏幕上。
具體的繪制時(shí)間跟DOM數(shù)以及應(yīng)用的樣式有關(guān)。有些樣式會花費(fèi)更多的執(zhí)行時(shí)間,比如復(fù)雜的漸變背景圖片所需要的計(jì)算時(shí)間遠(yuǎn)超過簡單固定背景色。
整合所有
想要看到關(guān)鍵渲染路徑的執(zhí)行流程,可以使用DevTools,在Chrome中,它是根據(jù)時(shí)間軸展示的。
舉個例子, 上面的頁面加入<script>
標(biāo)簽
<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>
可以看關(guān)于頁面加載時(shí)的事件日志,以下是我們獲得的:
- Send Request - 發(fā)送到index.html的GET請求
- Parse HTML and Send Request - 開始解析HTML并構(gòu)建DOM,然后發(fā)送 GET 請求style.css和main.js
- Parse Stylesheet - 根據(jù) style.css 創(chuàng)建的CSSOM
- Evaluate Script - 執(zhí)行 main.js
- Layout - 基于HTML的元視窗標(biāo)簽,生成布局
-
Paint - 繪制網(wǎng)頁
基于這些信息,我們可以知道如何優(yōu)化關(guān)鍵渲染路徑。