最近有個小困擾...關於網頁字體
嗯...最近在弄一些網頁的東西,然後一直覺得哪裡卡卡的。後來發現,是字體載入的問題。 🤔
你們應該有碰過吧?就是網頁打開了,內容都出來了,但上面的字,先是長得像「新細明體」或「標楷體」之類的系統預設字體,過個零點幾秒,才「啪!」一下,變回設計師精心挑選的那個漂亮字體。
那個瞬間的「跳動」,老實說... 有點煩。技術上好像叫 FOUT (Flash of Unstyled Text)。 還有更糟的是 FOIT (Flash of Invisible Text),就是字體下載好之前,你根本看不到任何文字,一片空白。 這兩種狀況,對使用者體驗都不太好,而且還會影響 Google 的一個評分,叫 Core Web Vitals。
所以,解法是?先跟瀏覽器「打小報告」
我研究了一下,發現一個滿簡單直接的方法,叫做「字體預加載」(Font Preload)。
簡單講,它就是在網頁的 `
` 區塊,放一個特殊的 `` 標籤。這標籤就像是提早跟瀏覽器打小報告:「喂,等等會用到一個很重要的字體檔,你現在沒事就先去下載,不要等到 CSS 都解析完了才去拿。」程式碼大概長這樣:
<head>
...
<link rel="preload" href="/fonts/your-custom-font.<a href="https://sia.codes/posts/making-google-fonts-faster/" target="_blank" class="blogHightLight_css nobox">woff2</a>" as="font" type="font/woff2" crossorigin>
...
</head>
這裡有幾個重點:
- `rel="preload"`:這就是主角,告訴瀏覽器「預先載入」這個資源。
- `as="font"`:這個很重要,明確告訴它這是個字體檔,瀏覽器會用比較高的優先級去處理它。
- `type="font/woff2"`:這是字體的格式,`woff2` 是目前壓縮率最高、最推薦的網頁字體格式。
- `crossorigin`:如果你的字體是放在別的伺服器上(例如 Google Fonts),那就一定要加這個屬性,不然瀏覽器會因為安全考量而忽略你的預加載指令。
這樣一來,瀏覽器在解析 HTML 的時候,就已經開始下載字體了,而不是等到讀完 CSS 才知道需要這個字體。理論上,這能縮短文字出現或變換的等待時間,進而改善 LCP (最大內容繪製) 這個 Core Web Vitals 指標。
不只 preload,還要搭配 `font-display` 屬性
只做 `preload` 其實只對了一半。另一個關鍵是 CSS 裡的 `@font-face` 規則,裡面有個屬性叫 `font-display`。 這個屬性是告訴瀏覽器,在自訂字體還沒下載完成時,該怎麼辦。
我把它整理成一個表,這樣比較好懂,用口語化的方式說:
| `font-display` 的值 | 發生什麼事? (使用者體感) | 適合用在哪? |
|---|---|---|
| `block` | 「...字咧?」畫面一片空白,等個幾秒字才跟內容一起出現。很容易造成 FOIT。 | 老實說...現在很少推薦用了。除非那個字體是 Logo 或非看不可的元素,不然等待的體驗不太好。 |
| `swap` | 「哦有字了!...咦?」會先看到系統預設字體,然後「啪」一聲換成載入好的美美字體。這就是 FOUT。 | 這是目前最常見的做法,先求有再求好。可以最快讓使用者看到內容,但缺點就是那個「跳動」可能會造成版面位移 (CLS)。 |
| `fallback` | 跟 `swap` 很像,但它會先空白個 100 毫秒左右,如果字體還沒好,就先用系統字。但如果等了大概 3 秒字體還沒來,它就放棄了,那一頁就一直用系統字了。 | 算是一種折衷。想用自訂字體,但又不想等太久。如果網路慢,就犧牲美觀換取穩定性。 |
| `optional` | 也是先空白一下下 (100毫秒),如果字體還沒好,就直接用系統字,而且「不會」再切換回來了。 | 效能優先!對於網路狀況不好的使用者最友善。如果這個字體只是點綴,不是非有不可,用這個就對了。保證不會有 CLS。 |
所以現在比較主流的作法是 `preload` 加上 `font-display: swap`,盡量讓字體早點載入,縮短那個「啪」一下的 FOUT 時間。
但... `swap` 真的那麼完美嗎?
我自己試了之後,覺得 `font-display: swap` 有個小陷阱。😬
雖然它讓文字很快出現,但因為「預設字體」和「你的網頁字體」通常長得不一樣寬、不一樣高,在切換的那個瞬間,整段文字的排版可能會亂掉。 比如原本只有一行的標題,換了字體後變成兩行,把下面的內容都往下推。這個「推擠」的動作,就是另一個 Core Web Vitals 指標:CLS (版面配置轉移)。
Google 的 web.dev 網站有提到,這是在效能和美感之間的取捨。 甚至在國外論壇像 Reddit 或 GitHub 上,都有開發者在抱怨 `swap` 造成的 CLS 問題很惱人。
特別是像日文或中文這種字元複雜的字體,一個檔案就很大,載入時間更久,`swap` 帶來的跳動感可能會更明顯。 這點跟英文字體那種幾十 KB 的小檔案狀況很不一樣。
所以,我的結論是...
想了一下,我覺得沒有「最好的」方法,只有「最適合的」。
- 最重要的字體才 Preload:不要貪心,把所有字體都預加載。`preload` 會佔用瀏覽器的頻寬,如果你預加載了一個其實沒那麼快用到的字體,反而會拖慢其他更重要的資源(比如圖片或 JS)。 像是網頁一打開第一眼就會看到的大標題字體,就值得 `preload`。
- 品牌或 Logo 字體用 `swap` 或 `fallback`:這些是體現品牌風格的關鍵,值得稍微犧牲一點點穩定性來確保它們能被顯示。
- 內文、長篇文章用 `optional`:對於一大段的內文,閱讀的流暢性比字體美觀更重要。用 `optional` 可以確保在網路不好的情況下,使用者能順暢閱讀,而且完全不會有版面跳動的問題。
- 終極大絕招:調整你的備用字體,讓它的大小、行高盡量跟你要用的網頁字體差不多。這樣就算發生 `swap`,跳動的幅度也會小很多,CLS 分數就會比較好看。
總之,這東西還真不是加一行 `preload` 就沒事了。😅 需要考慮的細節還不少。
不知道大家有沒有被這個字體閃爍問題困擾過?或者有什麼更好的優化技巧?可以在下面留言分享一下你的看法~ 👇
