網頁加載字型Web Font FOIT& FOUT與效能測試

Lucy
23 min readMar 6, 2019

前言

我是網頁設計師,主要工作是管理公司全站的CSS與網頁設計實作需求,除了基本的網站介面設計,以及持續關心的使用者經驗設計,現在待的這間公司,因為是在前端工程部門下,更需要了解前端以及效能的概念。

而這次要寫的主題是網頁加載字型(以下通稱web font)與效能的關係,主要是公司網站需要使用到五種web font! 遇到的FOIT(Flash Out Invisible Text)和FOUT(Flash Out of Style)的問題,要如何優化加載方式呢?

內容:中級
適合:懂CSS但是不太懂網站效能的設計師(就像我)
目標:找到加載五種網頁字型,又能平衡FOIT& FOUT與效能的加載方式

此篇會用以三支網路教學影片為主軸,大家可以跟我一起了解內容,因為我一開始不太熟悉效能的東西,也是聽了很多遍邊查資料,我會把我覺得重要的附件附上加上自己的重點解讀說明。

*不深入的地方或想幫忙補充的歡迎留言讓我知道~

基本名詞解釋

FOIT(Flash of Invisible Text)

字面直譯「閃爍」、「看不見的文字」,網頁加載的時候當字型加載太慢時,字跑不出來,當一完成家載後跳出字型會一閃一下,就是FOIT的情況。

FOUT(Flash of Unstyled Text)

Unstyle,非預期樣式的文字閃爍。同樣字型加載太慢時,瀏覽器先套用了其他字型當作代替,例如系統字型或預先下載好的替代字型,造成套用正確字型的樣式前後改變一閃的狀況,就是FOUT。

加載web font的六種方法 — FOUT&FOIT效能測試

Font Loading Performance 📉 6 Experiments with FOUT & FOIT

這是主管丟給我的一個hint video, 他針對web font的performance做實際的demo,來測試實際的結果,過程真的很淺顯易懂,遇到不懂的再去延伸查資料,這個方法意外的適合我!

這在之前大量在網路上搜尋資料有找到web font 字型大神!但是對於初學者來一下子掉進web font 海洋還是有點找不太到方向快溺死 💦 DevTips 是一個學習coding的youtube channel, 內容主要針對網站相關的前端技術學習,每週五都會上傳影片。

以下先介紹六種方法,最後我們在總結影片最後的結果:

1. web font loader sync 網頁字體裝載機— 同步加載

什麼是Web Font Loader?

Web Font Loader「網頁字體裝載機」轉成中文有種巨大的機器的感覺XD

簡單來說,就是一個提供很多關於web font加載功能的一個套件!只要include他們的JavaScript, 就可以去做不同的控制。所以Web Font Loader可以在所以設定成所有字型都下載後,再一起去reflow畫面,可以避免FOUT的情況。

Web Font Loader 讓你在透過 @font-face 使用鏈接字體過程,增加了可控制的能力。
它提供了共同的 “介面(機制)” 來下載字型,可以不用管來源是何處。
它提供了標準組合的事件 events,你可以用它來控制下載過程的體驗。
Web Font Loader 可以下載字型來自: Google Fonts, Typekit, Fonts.com, 和 Fontdeck, 以及自己伺服器的網路字型.
// 編按:Web Font Loader 是一段 Javascript 程式碼,下面是介紹和說明幾種使用的方式它是 GoogleTypekit 共同研發的.

Synchronous vs. Asynchronous 同步加載 vs. 異步加載

這個測試方法是sync(synchronous),下一個則是async(asynchronous) ,那我們來看看什麼是同步加載與異步加載web font。

假設現在有五支web font, 我們設定同步加載讓五支同時一起下載,優點是可以五支同時進行下載,會比較快速,但缺點是web font和css加載一樣會造成Render Blocking,網頁在CSSOM尚未建構完全時不會讓js起跑的。

What is Render-Blocking CSS?
在預設情況下,CSS 會被視為禁止轉譯的資源,只要 CSSOM 還未建構完成,即使內容已經過處理,瀏覽器也不會進行轉譯。請務必保持 CSS 簡潔、儘快提供 CSS,並使用媒體類型和媒體查詢來解除對轉譯作業的禁止令。

2. web font loader async 網頁字型裝載機 — 異步加載

異步加載則是透過設定分開進行下載,可以減緩Render Blocking,讓後面的js不會塞車。

3. self-hosted fonts 把字型存在本地端

self-hosted fonts, 「自己持有字型」,就是我們把字型直接放在自己的網站,這樣一來可以省去連到別的網站來回存取的時間;再來是假設使用者曾經造訪過使用相同字型的網站,而瀏覽器快取了這個字型,這樣的話就能更加速字型存取的速度!影片例子把google fonts下載下來,然後使用font-face設定字型。

4. self-hosted fonts with preload 把字型存在本地端 + preload 方法

什麼是preload?

這個方法是上面方法加上使用preload,使用preload方法可以以最優先的次序預先下載檔案,所以這邊的用法就是將字型資源先行preload下載下來!這樣在font-face使用時,就較不會有時差的問題。

Generally it is best to preload your most important resources such as images, CSS, JavaScript, and font files. This is not to be confused with browser preloading in which only resources declared in HTML are preloaded. The preload directive actually overcomes this limitation and allows resources which are initiated via CSS and JavaScript to be preloaded and define when each resource should be applied.

只要簡單的在<link>上加上標記 rel="reload"

<link rel="preload" href="https://example.com/fonts/font.woff" as="font" crossorigin>

同場加映preconnect

簡單來說,preconnect就是在發存取請求前預先連到目標網站,可以節省之後存取資源的時間!

Preconnect allows the browser to setup early connections before an HTTP request is actually sent to the server. This includes DNS lookups, TLS negotiations, TCP handshakes. This in turn eliminates roundtrip latency and saves time for users.

5. own font loader 本地端寫字型加載器

和第1、2種方法相似也是用JavaScirpt寫web font loader,但是前面提的是Google和TypeKit共同研發的,需要include他們的js,沒錯,就是要發外部request!這可能會是一個效能的關鍵點,所以第5種方法,就來測試如果在本地端使用自己寫的font loader的效果。

實作方式影片中是參考Zach Leatherman的FOUT with a Class, 使用CSS Font Loading API去偵測web font是否下載完成,完成了會在<html> tag上標記class,再去使用web font,避免web font 陸續完成而頻頻觸發unstlyled text。

6. own font loader with preload 本地端寫字型加載器 + preload方法

這邊就是上方自己寫font loader然後加上preload的方法!

測試成果與結論

基本測試環境設定:

  • Original基準樣本,是一般使用google fonts的方式:貼上從google fonts複製下來的<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,700"> 後,直接在css檔中使用字型。
  • 在chrome 開發者工具中,network設定成Slow 3G。
  • DevTips將所有樣本的結果製成逐格影片看哪一個樣本最先完整的print出內容且沒有FOIT和FOUT的情況。

這張圖是最後的測試成果,可以歸納出幾點!

1. Self-hosted fonts 壓倒性勝利!不管有沒有使用font loader都贏過使用Google 和 TypeKit官方的Web Font Loader,相差了快4秒

2. 其實不自己用js去寫font loader,直接使用preload方法就已經很快了!

3. 自己寫Font Loader也是一個有淺力的選擇

會這麼說是因為這支影片也有說,現在的demo影片其實很簡單,也許網頁內容一多會有不同的結果!

*小提醒:preload方法在這個例子中應該要更快,DevTips犯了syntax error所以效果沒有出來,影片上架後有被網友提醒,在下一支影片他有更正,但是沒有再做一次比較了!實際上加了preload應該會更快。

2019如何加載Web Fonts?

How to load Web Fonts in 2019

再來第二支,就是介紹新的CSS3新屬性 font-display: swap;

當web font 還沒完全下載可用時,可以用font-display去設定我們想要怎麼顯示我們的字型,而font-display: swap; 就是告訴broswer說,我們想要用替代字型,等真正的字型下載完畢再換上!

其他還有font-display: block; 告訴browser說等到下載完畢再用不然就顯示空白(變成FOIT的情況,也等同預設值);也有其他設定Value。

swap: Instructs the browser to use the fallback font to display the text until the custom font has fully downloaded. This is also known as a "flash of unstyled text" or FOUT.

測試成果與結論

影片把前一次的效果不錯的Self-hosted fonts + preload、和self-hosted own font loader + preload 都各自加一組對照組使用font-display: swap;在下面。

*小提醒:左上Baseline是使用系統字型完全不使用web font(當然最快!);左下是Late Preload + font-display: swap,是Zach Leatherman提到的另一個方法,在font-face宣告後才preload搭配font-display的測試。(有興趣可以看影片內容)

可以看到速度都差不多都是5秒左右,但是下面對照組有使用font-display: swap; FOIT的時間較短!字會先跑出來,這在網速低的情況下對閱讀者來說是較友善的。

結論:使用font-display: swap; 準沒錯!

看完兩支DevTips的影片稍微小結一下!

1. Self-hosted fonts或 self-hosted own web font loader

2. 使用preload

3. 使用font-display: swap; 縮短FOIT的時間

4. 除非是終極注重效能,可以使用self-hosted fonts + preload就可以了!

這段是DevTips的建議,因為font-loader都需要使用到JavaScript,在網頁跑JavaScript也會有額外的effort,以及還要針對font-loader去特別維護CSS,多少是一個心力。到這邊跟主管討論,我們覺得不用額外的為了web font去寫font loader,其實只是希望使用者讀起來舒服一點,不會在網速慢的時候看不到內容。

The Five Whys of Web Font Loading Performance | performance.now()2018

最終來到Zach Leatherman大大的建議啦,前兩支影片DevTips都有提到其實是看完Zach這支影片想要實際demo看看效能,所以我也把這支影片看了!這是2018在performace.now() 大會上的簡報,”Five Whys”其實不是五個為什麼是Five Y(五個web font字型的Y) 🤣 要如何加載五個web font又不影響效能呢?

直接打出解法,這支影片一步一步帶大家demo,我覺得必看!(用影片看蠻輕鬆的,如果要直接讀他github上的web font recipes 對新手來說可能會有點慌張!

1. Self Hosting

2. font-display: swap

3. preload 2支web font

4. sharding with preconnect 剩下3支使用sharding

5. CSS font loading API: 剩下3支使用sharding搭配CSS font loading API

preload五支web font造成嚴重rendering block

第1~3點剛剛的小節都有提到了,那我們來解釋為什麼會多出了4跟5,請看下圖前面很推的preload,但是現在有五支web font要load,我們知道web font檔案很大(每支至少20K以上,我們的有一支甚至到80K…),還要load五支果然會造成嚴重的rendering block,即使搭配了font-display: swap 效果也不會很好。

使用sharding分散web font到另一個伺服器

針對這個問題他提出了sharding(分片技術),其實查到的好像是資料庫分流的概念,沒有明確說是web font使用的專有名詞,所以就不解釋名詞,做法是把原本preload的五支本地端web font,分三支到別的伺服器preconnect(前面有提到的預先連接網站資源),這樣可以減緩本地端伺服器的effort,而Zach提到他的實驗是使用sharding真的可以幫助效能更快速存取web font。

如下圖他preload了兩支web font,然後preconnect到另一個伺服器(其中三支所在的伺服器)

<link rel=”preload” href=”https://performance-sometime.netlify.com/_fonts/notoserif.woff2" as=”font” type=”font/woff2" crossorigin=””> <link rel=”preload” href=”https://performance-sometime.netlify.com/_fonts/notoserif-bold.woff2" as=”font” type=”font/woff2" crossorigin=””> <link href=”https://performance-sometime-assets.netlify.com/" 
crossorigin=”” rel=”preconnect”>

然後在font-face的地方,可以看到src是分散兩個伺服器

<style>
/* latin 第二個伺服器*/
@font-face {
font-family: ‘Noto Sans’;
font-style: normal;
font-weight: 400;
src: url(https://performance-sometime-assets.netlify.com/notosans.woff2) format(‘woff2’);
unicode-range: U+0000–00FF, U+0131, U+0152–0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000–206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
/* latin 第二個伺服器*/
@font-face {
font-family: ‘Noto Sans’;
font-style: normal;
font-weight: 700;
src: url(https://performance-sometime-assets.netlify.com/notosans-bold.woff2) format(‘woff2’);
unicode-range: U+0000–00FF, U+0131, U+0152–0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000–206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
/* latin 第二個伺服器*/
@font-face {
font-family: ‘Noto Serif’;
font-style: italic;
font-weight: 400;
src: url(https://performance-sometime-assets.netlify.com/notoserif-italic.woff2) format(‘woff2’);
unicode-range: U+0000–00FF, U+0131, U+0152–0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000–206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
/* latin 本機端*/
@font-face {
font-family: ‘Noto Serif’;
font-style: normal;
font-weight: 400;
src: url(../_fonts/notoserif.woff2) format(‘woff2’);
unicode-range: U+0000–00FF, U+0131, U+0152–0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000–206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
/* latin 本機端*/
@font-face {
font-family: ‘Noto Serif’;
font-style: normal;
font-weight: 700;
src: url(../_fonts/notoserif-bold.woff2) format(‘woff2’);
unicode-range: U+0000–00FF, U+0131, U+0152–0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000–206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-display: swap;
}
</style>

也可以直接進這頁demo頁看source code。

CSS font loading API把剩下三個web font包成一個reflow

再來為了完全解決FOUT的問題,針對剩下三個web font使用font loading API也就是自己寫的font loader去控制他們在三支完全下載後才能使用,來解決三支的FOUT問題。

結論與自己的測試

前面提到跟主管討論想要以不使用js的情況下去優化web font效能,所以我綜合以上及自身情況,決定使用以下方法:

1. Self Hosting fonts

2. font-display: swap

3. preload 3支web font

根據自己網站的需求來決定,我們覺得先preload內容位置較明顯的3支

4. sharding with preconnect 剩下2支使用sharding

也學了DevTips做了實際測試,有個小圖表,測試前提:

  • 因為我們網站就是preload 五支web font 所以有做一些preload五支和三支的比較(我們選了比較重要的三支)
  • external css是因為,我們本來把font-face獨立一支CSS include,但我看前面三支影片都是用<style>直接寫在網頁中所以也特別做了比較
  • cdn就代稱另一個伺服器
  • 最下面那個例子比較特別,因為我們原本就有使用sharding的概念,但我們是把所有的web font相關都丟到cdn上,加上preload五支web font所以FOIT的情況比較嚴重,這個例子算是我們目前作法的對照,只是改成preload三支。
  • 以下全部都使用font-display: swap,除了最後一個例子

不負眾望的,Zach的做法表現不凡!跟沒有使用sharding比起來雖然有小小的差距,但我覺得可以視為誤差範圍內(也許他們一樣快!只是我在做影片demo的反應速度有影響到) 然後使用cdn分流的概念可以減輕網站的effort,所以我們最終就使用 internal css + 3preload +2sharding的做法!👏👏👏

--

--

Lucy

A front-end Web Designer in field of the digital content industry for 9+ years. Front-end/ Web /UX/ UI/ Service Design. www.linkedin.com/in/lucy-lee-a8180ab7