Oops…你的網站,也許不適合 Google Fonts API

Steven Yeo
《天下》數位敘事團隊
9 min readDec 28, 2022

不少網站為了讓整體風格一致,會選擇介接 Google Fonts API 使用有別於系統原生的字體,而思源黑體及思源宋體是中文網站常見的選擇。

使用 Google Fonts API 載入字體相當方便,只需要在 html 檔案中加入一小段 code:

<link href="https://fonts.googleapis.com/css2?family=FAMILY_NAME:wght@WEIGHT_OR_RANGE&display=swap" rel="stylesheet">

它會回傳一份所有字體 @font-face 規則 CSS 檔,好處是我們並不用一次載入整包字體檔案,瀏覽器會根據頁面上有出現的文字與這份檔案中的 unicode-range 做比對,再去下載相對應的字體子集檔案,達到「有需要再下載」的效果。

看起來這麼美好,《天下》的數位專輯為什麼不適合?

在說原因之前,先以前端的角度來提一下我們數位專輯的特性:

  • 頁面中有大量中文內容
  • 文中不時穿插資料視覺化圖表
  • 需要用到 5 種不同字重的 Noto Sans TC
  • 專輯架構主要是 Client-Side Rendering
  • 時常搭配 JS/CSS 產生特殊的轉場效果(滾動敘事、SVG 動畫)

這意謂著:

  • 一開始需要載入不少東西
  • 有時讀者滾動時也會觸發 JS/CSS 效果
  • 字體檔案就算是接 Google Fonts API 還是很大

在幾次專輯製作中發現,明明只用了些簡單的動畫效果,我們的頁面在移動裝置上滑動時常常會有 jank 的情形發生。

什麼是 jank?按照字面解釋有垃圾、不穩定的意思。在程式上,代表處理主要任務的機器過載了,所以讓後續的任務卡住,畫面無法順利更新。例如在下面的測試機示範畫面裡,當下滑時會出現畫面空白,N 秒之後畫面才會出現 。事實上,我們在JS/CSS 在寫法上都做了不少優化,等所有東西載完再滑也還是有一樣的狀況。

在內部測試機上,專輯「《人民日報》十年解讀習近平如何改變中國」曾出現的「網頁閃避」(jank)情形。

這對以內容為主的數位專輯影響很大,因為讀者可能會因為 jank 而錯過某些內容,嚴重影響閱讀體驗。

問題到底出在哪裡?

底圖擷取自 Rendering Performance

一般來說出現 jank 是因為有比較耗運算資源的工作堵住了瀏覽器的 main thread ,才會影響到整個 rendering pipeline 的運作,講白話一點就是渲染畫面的流程塞車了。

但用了監測工具看了滑動過程的狀況,特別針對 JS / Style / Layout 不同階段都做了寫法上的優化,問題還是依舊存在。(優化手法可以再開一篇來討論,篇幅關係請容我先直接帶過。)

正當我摸不著頭緒、開始亂試時,發現把整個 Google Fonts API 拿掉,讓頁面直接吃系統原生字體,可以顯著改善 jank 問題!

嗯,頁面在手機上變的超級無敵順,但頁面不能沒有字體存在啊。

為什麼拔掉字體檔案可以改善那麼多?說真的到現在我還不是很清楚確切的原因(歡迎留言討論。文末也附上我摸索的答案和參考資料),但至少確定字體檔案是一個值得嘗試優化的方向。

Google Fonts API 的中文字體

前面有提到 Google Fonts API 的做法是把多達數 MB 的單一字重字體檔切分成數百個大小約在 20~50 KB 的字體子集檔案,透過 @font-face 機制載入頁面上所需要用到的的子集就好了。

這是我們專輯經常使用 Google Fonts API 所提供的 @font-face CSS 檔案:https://fonts.googleapis.com/css?family=Noto+Sans+TC:100,300,400,500,700|Roboto:300,400,500,700&display=swap

從網址中其實就可以發現我們思源黑體就用了 5 種不同的字重,Roboto 則是用了 4 種,點開檔案後也可以知道 Google Fonts API 將上面的字體切割成 533 份字體子集。

實際載入時並不會 533 份子集都載入,只會載入頁面上所需要的字體子集。

中國20大專輯透過 Google Fonts API 載入字體的相關數據

可以發現實際需要載入的檔案其實不小,但因為已經嘗試過就算讓檔案全部載完還是無法解決 jank 問題,所以載入速度並不是這篇文章會關注的點。(檔案一定是越小越好,如果想優化整體字體載入策略,推薦 Harry Roberts 所寫的 The Fastest Google Fonts,他經常分享網頁效能的相關知識。)

如果用線上工具去檢視每份子集的檔案,可以發現以繁體中文的思源黑體中,每份子集約包含 200 多種字符,而這 200 多種字符在頁面中真的需要用到嗎?

其實不必然,所以我們可以針對字體檔案做幾個優化:

  • @font-face CSS 檔案可以更小( 目前 261 KB)
  • 減少字體子集檔案數量(目前 86 份)
  • 字體子集只包含頁面真正需要的字符,減少子集檔案大小(目前約 4 MB)

自製字體子集(font subseting):使用 pyftsubset

必須要自製字體子集才能做到前面所列舉出的優化,自製字體子集的工具有很多種,目前我們是使用 fonttools pyftsubset,它的優點是能調整的參數很多,因爲是使用 python 所撰寫的指令工具,可以進行相當高度的客製化,也方便與其他工具做整合。

使用教學相當推薦自由接案工程師 Markos Konstantopoulos 所寫的 Creating font subsets,他手把手教學如何使用 pyftsubset 的指令,也有詳細介紹字體檔案相關的背景知識,讓我們知道在產製字體子集時有哪些眉角需要注意。

中國20大專輯使用自產字體子集的相關數據:

與上一張圖對比,可以觀察到:

  • @font-face CSS 檔案大小:261 KB → 3.4KB
  • 減少字體子集檔案數量:86 → 10
  • 字體子集檔案大小:約 4 MB → 1.5MB

如果再針對字重來個別客製化子集,字體子集檔案大小估計還可以再減少 50% 以上。檔案大小是減少了很多,那最關鍵的 jank 問題有被解決嗎?有的!

左側是使用 Google Fonts API ,右側則是使用自產的字體子集

如果將手機連結至電腦使用效能工具進行監測:

可以發現使用 Google Fonts API 字體下,瀏覽器對物件佈局和樣式次數都比自產字體子集多上兩倍,從工具紀錄的截圖中也可明顯觀察到因為 jank 的關係中間有一段並沒有顯示出來,進而影響 CPU 用量以及 JS 的執行。

最後再把自產字體子集的功能整合進專輯開發流程,在專輯發布前解析最終版本的文字內容,就可以輕鬆產生輕量化的字體子集。

與我們的專輯開發系統 bulldog 進行整合

自產字體子集讓我們得到了效能,那有捨去什麼嗎?

捨去了一點彈性。

所以這種作法對需要經常進行文字更新的專輯(像是疫情動態)較不適合。
(現階段我們的數位專輯是採文/code分離,所以如果只更新文字的話,並不一定要整包專輯重新打包,詳情可以參考我們之前寫過的這篇文章。)

「測試」一詞並不包含在專輯的字體子集中

Google Fonts API 幾乎能涵蓋到所有中文字符,所以比較沒有掉字的困擾,那除了重新打包以外,有其他可能的解法嗎?

自產字體子集 x Google Fonts API

就算我們用了自產字體子集,只要繼續引入 Google Fonts API 的 @font-face CSS 檔案,還是可以透過 CSS 寫法上的優先級排序,把 Google Fonts API 當作備案使用。

當頁面上的字符在自定義的 CW-Noto-Sans-TC 子集中找不到相對應的檔案時,就可以透過 Noto Sans TC 去向 Google Fonts API 要它們的子集檔。

我的網站該自製字體子集嗎?

其實我們也並不是所有數位專輯都適合使用字體子集,就像前面所提到的,需要進行大量文字更新的專輯還是用 Google Fonts API 會方便許多,而且如果頁面對效能沒有什麼要求(像是我們的選舉網站),字體問題其實對讀者體驗其實影響不大。

在決定要自製字體子集前,覺得經營/開發網站的技術人員可以自問一些問題:

  • 網站在桌機及移動裝置上體驗順利嗎?
  • 網站目前有更明顯的問題還沒優化嗎?
  • 網站文字內容需要經常做動態更新嗎?
  • 對於眼前的效能問題已經窮途末路了嗎?🥲

不同字體來源影響效能的猜想

在文中有提到目前只能透過最終監測的結果,觀察到自產字體子集對移動裝置上頁面整體效能有極大的幫助,但為什麼會有幫助?在做了一些研究後還是沒辦法找到正確的答案,對此提供一些自己的猜想,希望大家可以一起集思廣益🤓:

  1. 移動裝置瀏覽器在遍歷大量 @font-face 規則時需要花不少效能。
  2. Google Fonts API 所提供的字體子集中,缺少一些 Layout features 讓移動裝置瀏覽器在光柵化( Rasterization )時候不順利。
  3. 不知明的原因與 JS/CSS 打架。

以下是小弟參考過的一些文章及文件供大家參考:

以上就是我們對於使用 Google Fonts API 及自產字體子集的經驗分享,歡迎大家一起討論!

--

--