前端效能優化筆記--非同步載入網頁中的 CSS

Eason Lin
7 min readDec 4, 2021

--

source: https://eng.taiwan.net.tw/m1.aspx?sNo=0030274

近期在專案上開始需要針對部分頁面提升其效能分數,因此開始研究如何提升前端效能,希望可以持續在不影響頁面既有功能及樣式的情況下,給使用者帶來更快速且舒適的體驗。

在前端開發上,我們透過撰寫 CSS 樣式來美化我們的頁面。隨著頁面越來越複雜,所需的 CSS 檔案容量越來越大,就會讓阻塞渲染的時間越來越長,進而影響到使用者實際看到畫面的時間。

什麼是阻塞渲染?

在瀏覽器取得頁面後,會開始建構 DOM 及 CSSOM 並合併成 Render Tree。在瀏覽器建構 CSSOM 時,瀏覽器會載入並解析頁面所需的 CSS,此時瀏覽器的渲染流程便會被阻塞,直到 CSS 加載並解析完成。

為什麼瀏覽器會在所需的 CSS 準備完成前阻塞渲染呢?

如果所需的 CSS 在準備完成前,瀏覽器就直接渲染了畫面,使用者首先看到的就會是完全沒有樣式的畫面,過了一下子又變成有樣式的,這個狀況被稱為「內容樣式短暫失效(FOUC)」。

在了解如何減少阻塞渲染的時間前,我們可以先簡單了解一下 <link> 這個 HTML 元素的用途。

<link>

<link> 元素最常被使用於 CSS 樣式表,例如:

<link href="/media/examples/link-element-example.css" rel="stylesheet">

較常使用的為 href, rel 這兩種屬性,rel 代表了關係(relationship),我自己把它解讀為「在這個網站中作為什麼被使用」;href 則定義了其資源所在的路徑。

以 rel 來說,較常使用的值為:

  • stylesheet — 樣式表
  • icon — 網站圖示
  • apple-touch-icon — 在 iOS 裝置下將頁面加入主畫面時,顯示的圖示(如下圖)
  • preload — 表示瀏覽器應預加載其資源

什麼意思呢?rel=”preload” 會告訴瀏覽器「請盡速下載並快取這份資源」。下載完後,瀏覽器不會對其做任何事情,因此我們需要用到 as 告訴瀏覽器「請將這份資源作為_來解析」。在下載完成後,再透過 onload 屬性告訴瀏覽器「在完成下載後,請將 rel 的值改為 stylesheet」。例如:

<link rel="preload"  href="css/style.css" as="style" onload="this.rel = 'stylesheet'" />

因為 rel=”preload” 載入的資源不會導致阻塞渲染,因此可以藉此實現「非同步載入 CSS」的效果,在載入 CSS 的同時,頁面的渲染工作也持續進行著。

若實際在 CSS 中使用,並開啟該頁面時,會發現瀏覽器先出現了僅有 HTML 的畫面,接著才出現套用過樣式的畫面,也就是最一開始所說的 FOUC。

我們能做的,就是將樣式拆分為 critical 及 non-critical,讓使用者進入網頁時會馬上看到區塊所需的 CSS 阻塞渲染,讓其餘非必要在渲染前就解析的 CSS 採非同步載入,例如:

<!-- 瀏覽器會載入此 CSS 直到完成後才繼續渲染 -->
<link rel="stylesheet" href="critical.css" />
<!-- 瀏覽器非同步載入的CSS -->
<link rel="preload" href="less-critical.css" as="style" onload="this.rel = 'stylesheet'" />

雖然 preload 使用上非常直觀,如果專案在支援度上需要概括 IE,就無法使用 preload 來實現非同步載入 CSS。在這樣的狀況下,我們可以考慮另一個辦法。

透過替換 media 屬性實現

當瀏覽器在解析頁面發現 stylesheet 時,會下載其 href 位址的資源。然而,若該 link 元素明確表示其使用情境時,在非該情境的狀況下,就不會對頁面渲染造成阻塞。

使用的方式很簡單:

<link rel="stylesheet" href="less-critical.css" media="print" onload="this.media='all'" />

這個 link 元素告訴瀏覽器:「這份 CSS 是列印頁面時才會用到,請在不停止渲染階段的情況下載入。」並且在載入完成後將 media 調整為 all,讓瀏覽器在所有情境中使用它。

可以使用純 JavaScript 實現這件事情嗎?

可以!做法就像我們手動掛載 script 一樣:

// 建立 link 元素
const mainCSS = document.createElement("link");
mainCSS.rel = "stylesheet";
mainCSS.href = "css/style.css";
// 將其置入 head 最末端
document.head.insertBefore(
mainCSS,
document.head.childNodes[document.head.childNodes.length - 1]
.nextSibling
);

但我相信絕大多數情境應該都是可以用 link 單純地達成需求。

--

--

Eason Lin
Eason Lin

Written by Eason Lin

Frontend Web Developer | Books

No responses yet