從接收 HTML、CSS、JavaScript 到實際繪製第一個畫面,瀏覽器中間做了哪些事?什麼會卡住瀏覽器繪製頁面?理解這條路徑是效能優化的根本。
建構視角的對應章節:瀏覽器渲染流程
概覽#
瀏覽器從拿到檔案到畫出像素的整套流程稱為關鍵渲染路徑(Critical Rendering Path, CRP)。本文聚焦在 CRP 的觀念、如何估算載入頁面所需的關鍵成本,以及如何降低它來加速首次繪製。
兩個衡量指標#
分析頁面載入的 CRP 時可以參考:
- 關鍵資源數(Critical Resources):開始顯示頁面前必須解析完畢的檔案數量。
- 最短關鍵路徑(Minimum Critical Path):估算關鍵資源載入時間用的最少 round trip 次數。發出請求到收到回應稱為一次 round trip,多個資源同時下載仍視為一次。
從載入到開始互動大致經過:
- 載入 HTML
- 開始解析 HTML
- 載入並解析其他檔案
- 繼續解析 HTML,重複上述步驟
- 全部解析完畢,開始轉譯頁面
範例拆解#
以下面這份 index.html 為例:
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div>Hello World!</div>
<img src="An awesome image.jpg" />
<script src="index.js"></script>
</body>
</html>開始轉譯前的流程:
- 解析至第 3 行:開始載入
style.css(阻擋轉譯) - 解析至第 7 行:把圖片載入排入排程
- 解析至第 8 行:開始載入
index.js(阻擋解析) style.css載入完成、開始解析index.js載入完成,等 CSS 解析完才執行- HTML 解析完成、開始轉譯
index.html、style.css、index.js 都必須處理完才能開始畫,因此關鍵資源為 3;CSS 與 JS 可同時下載,所以最短關鍵路徑是 2。
阻擋的兩種類型#
解析過程中會遇到 JavaScript、CSS、圖片等檔案,部分會造成 Render Blocking 或 Parser Blocking。
Render Blocking#
瀏覽器需要 HTML 與 CSS 才能繪出完整頁面。如果 HTML 一解析完就先畫一次,等 CSS 完成再重畫,使用者會看到一閃而過的無樣式畫面,也就是 Flash of Unstyled Content (FOUC)。為了避免,瀏覽器在解析完 CSS 前不會開始轉譯,CSS 過大或下載過久都會延後首次繪製。
Parser Blocking#
JavaScript 能新增或修改已解析的 DOM 元素,甚至插入 <link> 與 <script>,因此遇到 <script> 時,瀏覽器會把主執行緒交給 JavaScript 引擎執行(停止解析 HTML)。又因為 JavaScript 也會修改樣式,所以瀏覽器會等 CSS 解析完才執行 JavaScript,這也是為什麼常見把 <script> 放在文件最下方。
解除阻擋的策略#
降低檔案大小#
透過壓縮、tree shaking、code splitting 等手法縮小關鍵資源,減少下載與解析時間。
Inline#
把首屏所需的關鍵 CSS、JavaScript 直接寫進 HTML,省掉一次 round trip。
加上屬性提示#
例如行動裝置版的 CSS <link> 加上 media 屬性,避免桌機載入時被不必要的 CSS 阻擋;當 <script> 與 HTML、CSS 沒有直接關聯時,加上 async 或 defer 就不會阻止解析。
提早並行下載#
由於下載常常是最耗時的階段,盡可能讓關鍵資源越早開始下載越好;可借助 Lighthouse 找出 Critical Request Chains。
範例對照#
以下範例都在 Slow 3G 條件下,可比較關鍵資源與關鍵路徑的差異。
無 CSS 與 JavaScript#
<!DOCTYPE html>
<html>
<head>
<title>No CSS No JavaScript</title>
</head>
<body>
<img src="doge.png" />
<h1 class="main-title">No CSS & No JavaScript</h1>
</body>
</html>解析完 HTML 後馬上觸發 DOMContentLoaded 並繪出畫面,圖片載完後才觸發 onload。
- 關鍵資源:1
- 最短關鍵路徑:1
有 CSS、無 JavaScript#
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
<title>CSS & No JavaScript</title>
</head>
<body>
<img src="doge.png" />
</body>
</html><link> 的下載不會阻止 HTML 解析,但繪製要等 CSS 解析完成。
- 關鍵資源:2
- 最短關鍵路徑:2
有 CSS 與 JavaScript#
加入 <script src="index.js"></script> 後,因為 Parser Blocking,必須等 CSS 解析完並執行完 JavaScript 才觸發 DOMContentLoaded。
- 關鍵資源:3
- 最短關鍵路徑:2
CSS 加上 async JavaScript#
把 <script> 加上 async,HTML 解析完即可立即觸發 DOMContentLoaded,JavaScript 不再阻擋解析。
- 關鍵資源:2
- 最短關鍵路徑:2
Non-blocking CSS#
<link rel="stylesheet" href="print.css" media="print" /> 會被視為較低優先權的請求,且不會阻止 JavaScript 執行。
- 關鍵資源:2
- 最短關鍵路徑:2
結語#
降低關鍵資源數與最短關鍵路徑是加速首次繪製最直接的辦法,開發時應檢查是否引入了不必要的等待,並用對應的 unblocking 技巧減輕負擔。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10248286