前言
有時候前端開發需要使用到一些特殊字型,但宿主機上一般都沒有安裝相應的字型,所以需要將字型檔案與前端程式碼一起打包以及用 CSS 定義使用。本文主要是想回答一個問題:在效能方面,我們可以怎麼去最佳化前端需要載入的字型?
一般最佳化的思路主要是兩方面:
- 縮小字型檔案
- 最佳化字型載入的方式
縮小字型檔案
字型檔案一般都比較大,動不動就幾兆、十幾兆的,所以我們最佳化的第一步是想辦法縮減字型檔案的大小。
使用特定的字型格式——WOFF/WOFF2
常見的字型格式有TTF(TrueType Font)、OTF(OpenType Font)、EOT(Embedded Open Type)、WOFF(Web Open Font Format)和WOFF2。
更詳細的介紹字型的文章可以看:Web 字型簡介: TTF, OTF, WOFF, EOT & SVG
簡單來說,TTF和OTF都是沒有壓縮,所以這兩個格式的字型檔案會更大;EOT字型格式只有IE瀏覽器支援;SVG對文字的支援不夠好…等等,因此使用WOFF可以說是前端開發的最佳選擇了。(還有專家建議只用WOFF2字型,原文如下)
In fact, we think it is also time to proclaim: Use only WOFF2 and forget about everything else.
This will simplify your CSS and workflow massively and also prevents any accidental double or incorrect font downloads. WOFF2 is now supported everywhere. So, unless you need to support really ancient browsers, just use WOFF2. If you can't, consider not serving any web fonts to those older browsers at all. This will not be a problem if you have a robust fallback strategy in place. Visitors on older browsers will simply see your fallback fonts.
Bram Stein, from the 2022 Web Almanac
WOFF的優點主要有兩個:
- 字型經過壓縮,檔案大小一般比TTF小40%;
- 主流的瀏覽器新版本都支援此格式;
WOFF2是WOFF的升級版,壓縮率在此基礎上提高了30%。
按需壓縮字型
有時候前端頁面中需要使用特殊字型的文字是固定的,那麼就可以利用font-spider
(同類開源庫還有fontmin
等)去將文字所對應的字型子集提取出來,生成一個新的子集包代替原本的全量字型包,這樣即可大大減小字型檔案,提高效能。
使用方式
font-spider 可以自動分析出頁面中使用過的字型並進行按需壓縮,也就是說,如果頁面中的文字有改動就需要重新壓縮。這種壓縮方式比較適合頁面的文字一般不會改動的場景。
下面是我對某個字型檔案的提取對比,在這個情境中我的網站只有數字部分用到了這個字型,所以沒必要把整個字型檔案都打包進去,所以我使用了font-spider把字型檔案中的數字部分提取出來了。
字型壓縮前後的效果對比:
最佳化字型載入的方式
下圖展示了瀏覽器繪製頁面的過程:
從圖片我們可以看出,瀏覽器從請求html文件到呈現文字,中間還有好幾個步驟。因此字型的請求會比其他關鍵資源請求之後延遲很長時間,並且,在獲取到字型資源前,瀏覽器處理文字的方式也各不相同。
在載入網頁上的字型時,瀏覽器一般會有兩種選擇:
- 推遲渲染字型的時間直到字型資源下載完成(導致FOIT)。
- 在下載好字型資源前用備用字型展示該內容(導致FOUT)。
減少瀏覽器請求的次數
雖然在使用非預設系統字型時,使用者一般不會在本地安裝相應的檔案,但是開發者仍然需要考慮到這種情況。在定義 font-face
時,可以使用 local()
先請求本地字型,示例程式碼如下:
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome.woff2') format('woff2'),
url('/fonts/awesome.woff') format('woff'),
url('/fonts/awesome.ttf') format('truetype'),
url('/fonts/awesome.eot') format('embedded-opentype');
}
對於這段程式碼,瀏覽器會按照下述順序去請求資源:
- 瀏覽器渲染頁面的DOM樹,判斷出需要載入哪個字型。
- 迴圈判斷每個需要的字型是否可以本地使用。
- 如果本地沒有該字型,就按照程式碼順序去請求
url()
中定義的字型資源。- 如果有定義
format
,那麼瀏覽器在下載字型資源前會先檢查自己支不支援該格式,如果不支援,則繼續請求下一行程式碼定義的字型資源。 - 如果沒有定義
format
,則直接下載該資源。
- 如果有定義
使用 font-display
雖然不同的瀏覽器會對字型載入有不同的處理方式,但是開發者可以用 font-display
去控制這個行為。
font-display
在 CSS 層面上提供了此類問題的解決方法,它提供了五個屬性:
- auto:使用瀏覽器預設的行為;
- block:瀏覽器首先使用隱形文字替代頁面上的文字,並等待字型載入完成再顯示;
- swap:如果設定的字型還未可用,瀏覽器將首先使用備用字型顯示,當設定的字型載入完成後替換備用字型;
- fallback:與
swap
屬性值行為上大致相同,但瀏覽器會給設定的字型設定載入的時間限制,一旦載入所需的時長大於這個限制,設定的字型將不會替換備用字型進行顯示。 Webkit 和 Firefox 中設定此時間為 3s;- optional:使用此屬性值時,如果設定的字型沒有在限制時間內載入完成,當前頁面將會一直使用備用字型,並且設定字型繼續在後臺進行載入,以便下一次瀏覽時可以直接使用設定的字型。
採用什麼樣的載入策略取決於字型的重要性、個人審美以及效能考慮等等,因此在此不做推薦,以下是幾種主要策略:
效能優先 | 速度優先 | 網路字型優先 | |
---|---|---|---|
推薦值 | optional | swap | block |
優點 | 非常高效,文字呈現時間短。 | 可以保證在使用網路字型的前提下儘早完成字型的渲染。 | 可以最大程度確保優先使用網路字型;儘管也會出現佈局偏移,但是顯示效果比swap的效果更平滑。 |
缺點 | 如果瀏覽器在限制時間內沒有下載完字型,則不會載入網路字型。 | 如果字型請求過久,會導致頁面出現FOUT閃爍。 | 這個策略推遲了文字的顯示時間,導致FOIT閃爍。 |
綜上所述,開發者可以根據實際情況選擇不同的策略,例如font-display: swap
用於品牌和其他視覺上與眾不同的頁面元素;font-display: optional
用於正文中使用的字型。
自定義載入方式
如果開發者希望自定義字型載入的方式,並且願意承擔額外的js開銷,那麼還有以下選擇:
-
利用 字型載入API 去定義或者操作字型的載入行為(但是在舊瀏覽器中不可用)
例如,如果您確定需要特定的字型變體,您可以定義它並告訴瀏覽器立即啟動字型資源的獲取:
var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", { style: 'normal', unicodeRange: 'U+000-5FF', weight: '400' }); // don't wait for the render tree, initiate an immediate fetch! font.load().then(function() { // apply the font (which may re-render text and cause a page reflow) // after the font has finished downloading document.fonts.add(font); document.body.style.fontFamily = "Awesome Font, serif"; // OR... by default the content is hidden, // and it's rendered after the font is available var content = document.getElementById("content"); content.style.visibility = "visible"; // OR... apply your own render strategy here... });
此外,因為您可以檢查字型狀態(透過
check()
)方法並跟蹤其下載進度。 -
使用 Web Font Loader
只要在指令碼中新增下列程式碼,即可使用它所提供的載入功能:<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script> <script> WebFont.load({ google: { families: ['Droid Sans', 'Droid Serif'] } }); </script>
它同時支援同步載入和非同步載入,自由度較高。
-
其他方式
由於自定義的可能性實在是太多了,業內也有好幾個開源庫,因此在這裡不窮舉所有的實現方式。有興趣的朋友可以閱讀A COMPREHENSIVE GUIDE TO FONT LOADING STRATEGIES,How We Load Web Fonts Progressively以及網頁載入字型Web Font FOIT& FOUT與效能測試等文章。
預載入字型資源
上文提到了如何控制瀏覽器處理暫不可用的字型,除此之外,開發者還可以透過 preconnect
和 preload
加快字型資源載入的速度。
preconnect
如果開發者選擇在第三方站點上託管字型,那麼可以用 preconnect
預先與第三方來源建立連線,示例:
<head>
<link rel="preconnect" href="https://fonts.com">
<link rel="preconnect" href="https://fonts.com" crossorigin>
</head>
preload
使用 preload
可以讓瀏覽器優先下載字型資源,從而加快網頁顯示文字的速度,示例:
<head>
<!-- ... -->
<link rel="preload" href="/assets/Pacifico-Bold.woff2" as="font" type="font/woff2" crossorigin>
</head>
但是如果使用不當,預載入可能會對未使用的資源發出不必要的請求,從而導致效能損耗。
總結
最簡單直接的最佳化方式就是隻用系統字型(System Font)或者可變字型(Variable Font)來減少甚至清零所需要請求的網路字型的數量;如果不得不使用網路字型,可以試試上述方式去最佳化網頁效能。這些最佳化手段可以互相組合去使用,取得1+1>2的效果。
本文只是一個粗略的介紹,對於細節問題沒有過多涉及,歡迎在評論區補充更多資訊。