iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
3
Modern Web

你所不知道的各種前端 Debug 技巧系列 第 20

[Day 20] Performance - Critical Rendering Path

瀏覽器在繪製出整個頁面前大概經過了哪些步驟呢?什麼原因會阻止瀏覽器繪製頁面?

概覽

理解瀏覽器如何運作是網頁效能優化最重要的基礎,從接收到 HTML、CSS、JavaScript 等檔案到繪製出每個像素需要做哪些事情,這一連串的流程就稱為關鍵轉譯路徑(Critical Rendering Path, CRP),本文將會著重在 CRP 的觀念以及如何計算、降低載入頁面的 CRP 來提升網頁效能。

而瀏覽器的詳細轉譯過程會在 Performance - How Rendering Works 解釋。

載入網站

想要分析頁面載入的 CRP,可以使用以下兩個指標:

  • 關鍵資源數(Critical resources) -- 開始顯示頁面前須解析完畢的檔案數量。
  • 最短關鍵路徑(Minimum critical path) -- 最少 Round trip 次數,發出請求至收到回應稱為一次 Round trip,若兩個資源同時發出請求也視為一次 Round trip,用來估計關鍵資源的總載入時間。

使用者從載入網站到開始互動前大概會經過以下步驟:

  • 載入(Load) HTML
  • 開始解析(Parse) HTML
  • 載入、解析其他檔案
  • 繼續解析 HTML,重複以上步驟
  • 檔案都解析完畢,開始轉譯(Render)頁面

以這份 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>

開始轉譯網頁前的流程將會是:

  1. 解析 HTML 至第 3 行時,開始載入 style.css (阻止頁面轉譯)
  2. 解析 HTML 至第 7 行時,將圖片的載入加入排程
  3. 解析 HTML 至第 8 行時,開始載入 index.js (阻止 HTML 解析)
  4. style.css 載入完成,開始解析
  5. index.js 載入完成,開始執行 (須等 style.css 解析完畢)
  6. 解析 HTML 完成,開始轉譯頁面

可以看到必須等 index.htmlstyle.cssindex.js 都解析完畢才能開始顯示頁面,因此有 3 個關鍵資源,由於 style.cssindex.js 可以同時下載,最短關鍵路徑為 2。

很明顯關鍵資源關鍵路徑是影響頁面顯示時間的關鍵,數字越大可以開始轉譯頁面的時間就越慢,但是甚麼原因會提升這兩個數字呢?

阻塞(Blocking)

解析 HTML 的過程中會有許多需要載入的檔案,例如 JavaScript、CSS、圖片等等,其中某些檔案會造成阻塞讓瀏覽器無法開始轉譯,而阻塞又分為兩種,Render blocking 以及 Parser blocking。

Render Blocking

瀏覽器需要 HTML 和 CSS 才能繪製出完整的頁面,若在解析完 HTML 當下馬上畫出頁面,等到解析完 CSS 再重新畫出一版頁面,使用者就會看到螢幕閃過幾乎無法閱讀的文字畫面,接著再變為加入 CSS 的頁面,這種現象稱為 Flash of Unstyled Content (FOUC)。

為了避免 FOUC 影響使用者體驗,瀏覽器在解析完 CSS 前會阻止轉譯,CSS 檔案越大、下載時間越久都會延遲瀏覽器能夠開始轉譯頁面的時間。

模擬 FOUC,左為沒有 CSS 的頁面。

Parser Blocking

為了讓頁面互動性更強,現在的網頁幾乎少不了 JavaScript,也是因為 JavaScript 如此強大能夠操作頁面中的元素、樣式,也成為了繪製頁面的限制。

由於 JavaScript 過於動態,能夠新增或修改 HTML 已解析的元素,甚至是加入 <link><script>,因此遇到 JavaScript 時瀏覽器會將主線程的控制權從解析 HTML 交給 JavaScript 引擎(阻止解析),執行完畢後再繼續解析 HTML。

但別忘了 JavaScript 還能修改樣式,所以瀏覽器會等到 CSS 都下載、解析完畢才開始執行 JavaScript,這也是為什麼常常看到 <script> 被放在 HTML 的最下方。

Unblocking

Unblocking 的方式有很多,但主要圍繞在以下幾個重點:

降低檔案大小

用壓縮、Tree shaking、Code spliting 等方式降低關鍵資源的大小,加快下載和解析速度。

Inline

把轉譯頁面所需的 CSS、JavaScript 直接寫入 HTML,降低 Round-trip。

加入屬性

例如把手機板的 CSS <link> 加上media 屬性,避免使用電腦時被不必要的 CSS 阻止轉譯。

如果 JavaScript 的執行和 HTML、CSS 無直接關係,在 <script> 加上 asyncdefer 屬性後就不會阻止解析。

提早、並行下載

盡可能的讓關鍵資源越早開始下載越好,因為下載通常是花費最長時間的部分。

另外可以用 Lighthouse 找出 Critical Request Chains

實際操作

知道了 Blocking 和 Unblocking 的基本觀念後,直接來看看實際的例子吧,為了讓結果更明顯,以下範例皆將低網速為 Slow 3G。另外也可以試著計算關鍵資源關鍵路徑各為多少。

以下範例皆在 Demo 頁面 Critical Rendering Path 中,可以實際操作觀察結果。

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

最簡單的狀況,沒有 CSS 和 JavaScript,解析完 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>

加入 CSS 後,遇到 <link> 後開始下載 CSS,但不影響 HTML 的解析,立即觸發 DOMContentLoaded,下載並解析完 CSS 後繪製出頁面。

  • 關鍵資源:2
  • 最短關鍵路徑:2

有 CSS、有 JavaScript

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <title>CSS & JavaScript</title>
  </head>
  <body>
    <img src="doge.png" />
    <script src="index.js"></script>
  </body>
</html>

加入 CSS 和 JavaScript,因為 Parser blocking,等到解析完 CSS 並執行完 JavaScript 後才觸發 DOMContentLoaded,開始繪製頁面。

  • 關鍵資源:3
  • 最短關鍵路徑:2

有 CSS、Async JavaScript

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <title>CSS & Async JavaScript</title>
  </head>
  <body>
    <img src="doge.png" />
    <script src="index.js" async></script>
  </body>
</html>

利用 Async 屬性避免 JavaScript 阻止解析,解析完 HTML 後立即觸發了 DOMContentLoaded

  • 關鍵資源:2
  • 最短關鍵路徑:2

Non-blocking CSS、 JavaScript

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="print.css" media="print" />
    <title>CSS & Async JavaScript</title>
  </head>
  <body>
    <img src="doge.png" />
    <script src="index.js"></script>
  </body>
</html>

Non-blocking CSS 被視為較低優先權的 Request 且 JavaScript 不需要等 CSS 解析完畢才執行。(這邊故意增加 CSS 的內容,讓結果更明顯)

  • 關鍵資源:2
  • 最短關鍵路徑:2

 

結語

透過降低關鍵資源和最低關鍵路徑能有效的加快繪製出頁面的時間,開發網頁時記得檢查是否造成了不必要的等待,並用對應的 Unblocking 方式來加快頁面繪製速度。


上一篇
[Day 19] Performance - Web Vitals
下一篇
[Day 21] Performance - How Rendering Works
系列文
你所不知道的各種前端 Debug 技巧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言