網站性能優化–CRP
為了把HTML、CSS和JavaScript轉化成活靈活現、絢麗多彩的網頁,瀏覽器需要處理一系列的中間過程,優化性能其實就是了解這個過程中發生了什么-即CRP(Critical Rendering Path,關鍵渲染路徑)。首先,我們從頭開始快速學習一下瀏覽器是如何顯示一個簡單網頁的。
瀏覽器渲染一個網頁的過程
構建對象模型
文檔對象模型(DOM)
<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的原始字節,然后根據指定的文件編碼格式(例如 UTF-8)將其轉換為相應字符
- 令牌化:瀏覽器把字符轉化成W3C HTML5 標準指定的各種確切的令牌,比如””、””以及其他在尖括號內的字符串。每個令牌都有特殊的含義以及它自己的一套規則
- 詞法分析:生成的令牌轉化為對象,這個對象定義了它們的屬性及規則
- DOM構建:最后,由于HTML標記定義了不同標簽之間的關系(某些標簽嵌套在其他標簽中),創建的對象在樹狀的數據結構中互相鏈接,樹狀數據結構也捕獲了原始標簽定義的父子關系:HTML對象是body對象的父對象,body是p對象的父對象等等
上述整個流程的最終輸出是文檔對象模型,即這個簡單網頁的 “DOM”,瀏覽器完成頁面的所有后續處理都是建立在這個DOM基礎上的。
打開Chrome DevTools > Timeline,錄制時間軸,上述過程對應Loading事件中的Parse HTML事件,可以查看到執行這一過程所需要的時間。
DOM樹捕獲了文檔標記的屬性及關系,但它沒有告訴我們元素在渲染時是什么樣子的。這是CSSOM要干的活,也就是接下來要講的。
CSS對象模型(CSSOM)
當瀏覽器構建上述網頁DOM的時候,在head里面碰到一個link標簽,這個標簽引用了一個外部的CSS樣式表:style.css。瀏覽器預測會需要這個資源來渲染頁面,因此會立即發出一個該資源的請求,該請求返回以下內容:
與HTML一樣,我們需要將收到的 CSS 規則轉換為瀏覽器可以理解、能夠處理的東西。因此,我們重復與處理 HTML 非常相似的過程:
最終輸出的是CSS對象模型,即CSSOM。
想要查看CSS處理過程所花費的時間,可以在錄制的時間軸中查看Rendering事件中的Recalculate Style事件:與DOM解析不同,timeline不顯示單獨的“Parse CSS”條目,而是在Recalculate Style事件下一同捕獲CSS解析、CSSOM構建以及computed styles的遞歸計算。
構建渲染樹、布局及繪制
前面介紹了我們根據輸入的HTML及CSS構建了DOM樹和CSSOM樹,但二者是獨立的對象:DOM描述的是文檔內容,CSSOM描述的是應用于文檔的樣式規則。瀏覽器會把DOM和CSSOM組合起來構建一個渲染樹(Render-tree),渲染樹會捕獲頁面上所有可見的DOM內容以及應用在每個節點上的CSSOM樣式。
構建渲染樹的過程大致如下:
1.從DOM樹的根節點開始,遍歷每個可見的節點
- 某些節點不可見(例如 script 標簽、meta 標簽等),因為它們不會體現在渲染結果中,所以會被忽略
- 某些通過 CSS 隱藏的節點在渲染樹中也會被忽略,比如應用了 display:none 規則的節點
2.為每一個可見的節點匹配并應用對應的CSSOM規則
3.生成有內容和計算樣式的可見節點
注意:小提示:注意visibility: hidden和display: none二者的區別。visibility: hidden只是讓元素在視覺上不可見,但是元素在頁面布局中仍然占據空間。而display: none則是從渲染樹中刪除某一個元素,不僅視覺上不可見,渲染樹上也沒有,更不會影響到頁面的布局。
最終輸出的就是一個包含了所有可見節點的內容及樣式信息的渲染樹。
到目前為止,我們已經計算出了哪些節點是可見的以及它們的計算樣式,但是我們還沒有計算它們在設備視口(viewport)中的準確位置及尺寸大小——這就是布局(Layout)階段要做的工作,也就是常說的重排(reflow)。
為了計算出頁面中每個對象的準確大小和位置,瀏覽器從渲染樹的根節點開始遍歷,計算頁面上每個對象的幾何信息。舉例如下:
<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>
上面頁面的 body 包含兩個嵌套 div:第一個 div(父元素)將節點尺寸大小設置為視口寬度的 50%,第二個 div 的寬度為父元素的 50%,即視口寬度的 25%!
布局過程的輸出是一個“盒子模型”,它精確地捕獲了每個元素在視口中的準確位置及尺寸大小:所有相對度量單位都被轉換為屏幕上的絕對像素。
自此,我們已經知道了哪些節點是可見的以及它們的計算樣式和幾何信息,然后我們就可以把這些信息傳送到最后一個階段,即把渲染樹中的每一個節點都轉化到屏幕上實際的像素點。這個步驟通常被稱為繪制(painting)或者柵格化(rasterizing)。
構建渲染樹、布局與繪制所消耗的時間也可以通過timeline來查看:
- “Layout” 事件捕獲渲染樹的構建及位置、尺寸的計算
- 布局完成時,瀏覽器會觸發 ‘Paint’ 事件:將渲染樹轉化為屏幕上的實際像素
終于,我們的頁面在設備視口中可見了。
現在回顧一下瀏覽器執行的幾個步驟:
- 處理 HTML 標記,構建 DOM 樹
- 處理 CSS 標記,構建 CSSOM 樹
- 將 DOM 樹和 CSSOM 樹融合成渲染樹
- 根據渲染樹進行布局,計算每個節點的幾何信息
- 在屏幕上繪制各個節點
優化CRP
阻塞渲染的CSS
在構建渲染樹部分我們已了解到:CRP要求DOM和CSSOM兩者融合在一起才能構建渲染樹。這就導致了一個性能問題:HTML和CSS都是阻塞渲染的資源。HTML很顯然,沒有DOM就沒有內容去渲染。CSS沒有那么明顯,但確實是阻塞渲染的資源。我們知道一個正常的網頁如果沒有引入專用的css,頁面有多丑陋。當我們的網頁引入了專用的css,頁面一加載出來的時候就是絢麗多彩的,如果css不阻塞渲染,我們看到的很可能是這樣的一個畫面:頁面剛加載出來的時候其丑無比,過了一會,頁面又變漂亮了……
既然CSS是阻塞渲染的資源,這就意味著在CSSOM構建完成之前,瀏覽器不會去渲染任何已處理的內容。要盡早、盡快地把CSS下載到客戶端以優化首次渲染的時間。
使用CSS“媒體類型”和“媒體查詢”優化阻塞渲染的CSS:
<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
- 第一條聲明阻塞渲染,匹配所有情況
- 第二條聲明只適用于打印(媒體類型),因此,頁面在瀏覽器中首次加載時,不會阻塞渲染
- 第三條聲明提供了媒體查詢,由瀏覽器判斷:如果條件符合,則在該樣式表下載并處理完以前,瀏覽器會阻塞渲染
小提示:「阻塞渲染」僅是指該資源是否會阻塞瀏覽器的首次頁面渲染。無論 CSS 是否阻塞渲染,CSS 資源都會被下載,只是說非阻塞性資源的優先級比較低而已。
阻塞解析的JavaScript
js可以修改頁面的內容、樣式以及響應用戶的交互,JS在DOM、CSSOM和JS執行之間引入了很多新的依賴關系,導致瀏覽器在處理和渲染頁面上出現大幅延遲:
- 當瀏覽器遇到
<script>
標簽時,DOM構建會暫停,直到腳本執行完畢 - JavaScript 執行會暫停,直到CSSOM準備就緒
解析器阻塞 vs. 異步 JavaScript
默認情況下,JavaScript 執行會阻塞解析器:當瀏覽器在文檔中遇到<script>
標簽時,DOM構建必須暫停,瀏覽器把控制權移交給JS引擎,JS引擎編譯并執行腳本,腳本執行完畢后再繼續構建DOM。
事實上,內聯腳本始終會阻塞解析器,除非你編寫額外的代碼來延遲它們的執行。那通過<script>
引入的外聯腳本呢?結果是一樣的,瀏覽器都會暫停,然后執行腳本,腳本執行完畢之后再去處理文檔的剩余部分。盡管如此,通過<script>
引入外聯腳本還是有一個很大的好處。
默認情況下,所有 JS 均會阻塞解析器,因為瀏覽器不知道腳本想在頁面上做什么,因此它必須假定最糟的情況并阻塞解析器。但是,如果我們能夠有個信號告知瀏覽器,說腳本無需在文檔中引用它的確切位置被執行呢?這樣一來,瀏覽器就會繼續構建DOM,并在腳本準備就緒后執行腳本。
這個信號就是async——在script標簽里面添加async關鍵字,其有兩個特性:
- 告訴瀏覽器當它碰到<
script>
標簽時不用阻塞DOM構建,因此瀏覽器會忽略腳本請求,繼續解析DOM - JS執行不依賴CSSOM:如果在CSSOM就緒之前腳本已經就緒,腳本可以立即執行
很顯然,這將會顯著提升性能!
分析CRP性能
先定義三個用于描述CRP的詞匯:
- 關鍵資源:能夠阻止網頁首次渲染的資源
- 關鍵路徑長度:往返過程的數量,或者獲取所有關鍵資源所需的總時間
- 關鍵字節:網頁首次渲染所需的總字節數,是所有關鍵資源的傳輸文件大小總和
demo1:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Critical Path: No Style</title></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div></body>
</html>
最簡單的可用網頁僅由 HTML 標記組成:無 CSS、javascript 或其他類型的資源。要呈現此網頁,瀏覽器必須初始化請求、等待 HTML 文檔準備就緒、對其進行解析、構建 DOM,最后使其呈現在屏幕上。
- 1個關鍵資源
- 1個關鍵路徑長度(假設文件很小)
- 5KB關鍵字節
T0 和 T1 之間的時間用于捕獲網絡傳輸和服務器處理時間。 在最理想的情況(HTML 文件較小)下,我們僅需一個網絡往返過程即可提取整個文檔(由于 TCP 傳輸協議的工作方式,較大的文件可能需要多個往返過程)。
demo2:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div></body>
</html>
- 2個關鍵資源
- 2個或更多個關鍵路徑長度
9KB關鍵字節
我們必須同時使用 HTML 和 CSS 來構建渲染樹,因此 HTML 和 CSS 均為關鍵資源;瀏覽器需要一個網絡往返過程來提取 HTML 文檔,然后檢索到的標記告知我們還需要 CSS 文件,這意味著,瀏覽器必須返回服務器并獲取 CSS,然后才能在屏幕上呈現網頁,因此,該網頁最少需要兩個往返過程才能顯示(CSS 文件可能需要多個往返過程,重點在’最少’);兩種資源加起來的關鍵字節總量最多為 9 KB。
demo3:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js"></script></body>
</html>
- 3個關鍵資源
- 2個或更多個關鍵路徑長度
11KB關鍵字節
我們有三個關鍵資源,關鍵字節總量最多為 11 KB,但是關鍵路徑長度仍然是兩個往返過程,因為我們可以并行傳輸 CSS 和 JavaScript!
demo4: 如果app.js中的內容不涉及到操作DOM和CSSOM,只是一些分析類型的代碼和其他不需要阻塞頁面渲染的代碼,則可以在<script>
中加入“async”屬性:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js" async></script></body>
</html>
異步執行腳本有以下幾項優勢:
- 腳本再也不會阻止解析器,所以也不再是CRP的組成部分
- 因為沒有其他關鍵腳本,CSS 也不需要阻止DomContentLoaded事件
- DomContentLoaded事件觸發得越早,其他應用邏輯執行的時間就越早
因此,經過優化的網頁恢復到了具有兩個關鍵資源(HTML 和 CSS)、具有兩個往返過程的最短關鍵路徑長度和 9 KB 的關鍵字節總量。
demo5:
如果CSS樣式表僅適用于打印:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet" media="print"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js" async></script></body>
</html>
因為 style.css 資源僅用于打印,所以只要DOM構建完成,瀏覽器就具有了渲染網頁的足夠信息! 所以,該網頁僅具有一個關鍵資源(HTML),最小關鍵呈現路徑長度為一個往返過程和5KB的關鍵字節。
優化CRP
常規步驟:
- 分析、描述關鍵路徑:關鍵資源數量、字節數、關鍵路徑長度
- 最小化關鍵資源數量:刪除相應資源、延遲下載、標記為異步資源等
- 減少關鍵字節數,以減少資源下載時間(往返次數)
- 優化剩余關鍵資源的加載順序:盡可能早的下載所有關鍵資源,以縮短關鍵路徑長度
文章轉載自https://segmentfault.com/a/1190000008550336