CSS @font-face效能優化

騰訊IVWEB團隊發表於2019-03-05

概述

本文主要介紹字型載入優化的常用策略,大部分內容為引用和翻譯。

一、 font-face基本用法

font-face的基本用法想必大家都是知道的,基本上就是類似這樣:

@font-face {
	font-family: Lato;
	src: url('font-lato/lato-regular-webfont.woff2') format('woff2'),
		 url('font-lato/lato-regular-webfont.woff') format('woff'),
		 url(font-lato/lato-regular-webfont.ttf) format("opentype");
}
p { font-family: Lato, serif; }
複製程式碼

這樣就可以使我們的網頁用上自定義字型了。 除了font-family 和 src屬性之外,還擁有font-style以及font-weight屬性。 src可以指定多種字型,會按順序依次適用,比如上面的示例中會先載入woff2字型,如果失敗再載入woff字型,否則載入opentype字型。 src所支援的字型可以有以下型別:

CSS @font-face效能優化

src引數帶不帶引號都可以,引數的格式不同含義也不盡相同,比如下面:

src: url(fonts/simple.woff);       /* 載入simple.woff,地址相對於樣式表的地址 */
src: url(/fonts/simple.woff);      /* 載入simple.woff,地址是網站的絕對地址 */
src: url(fonts/coll.otc#foo);      /* 從coll.otc字符集中載入foo字型 */
src: url(fonts/coll.woff2#foo);    /* 從coll.woff2字符集中載入foo字型 */
src: url(fonts.svg#simple);        /* 載入id 為'simple'的SVG字型 */
複製程式碼

src中載入的字型地址受跨域的約束,如果想跨域載入字型,需要設定CORS。

這就是font-face的最基礎的用法。 接下來我們會進一步分析font-face的用法,並儘可能的找出優化策略。

二、 什麼時候會下載字型?

上面講了字型的基本知識,那你有沒有想過,字型是在什麼時候下載的呢?當我們僅僅在CSS中定義如下樣式的時候, 頁面載入,字型會自動下載嗎?

@font-face {
	font-family: Lato;
	src: url('font-lato/lato-regular-webfont.woff2') format('woff2'),
		 url('font-lato/lato-regular-webfont.woff') format('woff'),
		 url(font-lato/lato-regular-webfont.ttf) format("opentype");
}
複製程式碼

很遺憾,字型並不會下載。 通常情況下,只有當我們的頁面元素用到了font-face中定義的字型的情況下,才會下載對應的字型。

注意: 這裡我們說了是通常情況,這是因為,IE8在只要是定義了font-face,即使頁面元素沒有使用對應的字型,也會下載。

在其它瀏覽器中也不盡相同,

比如在FirefoxIE 9+ 中,遇到如下情況也會下載字型:

html

<div id="test"></div>
複製程式碼

css

#test {
	font-family: Lato;
}
複製程式碼

有什麼特別之處呢? 你可能注意到了,這個元素雖然使用到了font-family: Lato樣式,但是這個元素並沒有任何文字啊!!!。 按照我們的理想情況,應該是,只有有文字內容才會去下載字型嘛。 而這就是Chrome, Safari (WebKit/Blink 等)瀏覽器的行為。

Chrome, Safari (WebKit/Blink 等)瀏覽器只有在如下類似情況才會去下載字型:

html

<div id="test">這裡是有文字的哦</div>
複製程式碼

css

#test {
	font-family: Lato;
}
複製程式碼

所以總結一下,不同瀏覽器下載字型的策略:

  • IE8 只要定義了font-face,就會去下載字型,不論實際有沒有應用該字型。
  • Firefox, IE 9+ 只有定義了font-face 並且頁面有元素應用了該字型,就會去下載,不論該元素是否有文字內容。
  • Chrome, Safari 只有定義了font-face 並且頁面有元素應用了該字型,並且該元素有文字內容,才會去下載字型。

那你可能會問了,如果我們的DOM元素是通過動態插入的呢?比如:

var el = document.createElement('div');
el.style.fontFamily = 'open_sansregular';
document.body.appendChild(el);
el.innerHTML = 'Content.';
複製程式碼

答案是一樣的,它的下載策略如下:

var el = document.createElement('div');
el.style.fontFamily = 'open_sansregular';
/* 到這裡,IE8就會開始下載字型 */

document.body.appendChild(el);
/* 只有到這裡,Firefox, IE 9+ 才會開始下載字型 */

el.innerHTML = 'Content.';
/* 只有到這裡,Chrome, Safari 才會開始下載字型 */
複製程式碼

三、 FOIT(Flash of Invisible Text)

FOIT是瀏覽器在載入字型的時候的預設表現形式,也就是在字型載入過程中,頁面是看不到文字內容的。在現代瀏覽器中,FOIT會導致這種現象出現至多3秒。FOIT會導致很差的使用者體驗,這是我們需要儘量去避免的。

四、 FOUT(Flash of Unstyled Text) 與 font-display屬性

FOUT意思是在字型載入過程中使用預設的系統字型,字型載入完後顯示載入的字型,如果超過了FOIT(3s)字型還沒載入,則繼續使用預設的系統字型。

IE瀏覽器和Edge不會等待FOIT超時才顯示預設字型,會立即顯示預設字型。FOUT比FOIT好,但是需要注意它引起的reflow.

那麼要想使瀏覽器有FOUT行為,我們需要在設定@font-face的時候給它加一個屬性:font-display。 font-display預設是auto, 可選屬性與含義如下:

  • auto. The font display policy is user-agent-defined.
  • block. Gives the font face a short block period (3s is recommended in most cases) and an infinite swap period.
  • swap. Gives the font face an extremely small block period (100ms or less is recommended in most cases) and an infinite swap period.
  • fallback. Gives the font face an extremely small block period (100ms or less is recommended in most cases) and a short swap period (3s is recommended in most cases).
  • optional. Gives the font face an extremely small block period (100ms or less is recommended in most cases) and a 0s swap period.

一般設定成fallback和optional即可。

五、 preload

在頁面加入下面這個程式碼以便更快的載入字型:

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
複製程式碼

通常和最基本的字型用法配合使用

六、 字型轉 BASE64URI

這種方法就是將@font-face中定義字型時的路徑直接改為字型的base64編碼。

優點: 這種做法的優點是不會產生FOIT和FOUT。所以也不會有reflow和repaint. 缺點: 字型轉成base64也會很大,會影響頁面首次載入速度。不支援逗號分隔的形式載入多種格式的字型,只能載入一種格式字型。這導致你為了儘可能保證所有瀏覽器都可以相容,通常會指定為woff格式,因為woff格式相容性好,但是卻沒法使用更小體積的woff2格式,因為woff2格式相容性差點。

七、非同步載入BASE64格式URI字型

這種方法就是通過非同步的方式插入帶有BASE64格式URI字型的CSS連結。

八、使用Font Load API + FOUT + class切換

這種方式是期初並不使用用到@font-face的class,然後用Font Load API載入我們想用的字型,然後切換相應的CSS即可。Font Load API是原生的API:

document.fonts.load('1em open_sansregular')
.then(function() {
	var docEl = document.documentElement;
	docEl.className += ' open-sans-loaded';
});

.open-sans-loaded h1 {
	font-family: open_sansregular;
}
複製程式碼

當然這種方法需要考慮瀏覽器相容性的問題。

九、 FOFT(Flash of Faux Text)

FOFT會把字型的載入分成多個部分,首先載入羅馬網路字型,然後會在載入真實的粗體和斜體的時候立即使用font-synthesis屬性渲染粗體和斜體的變體。

這種方法是基於[使用Font Load API + FOUT + class切換]這種方式的,非常適合載入同一種字型但是不同粗細,字形的場景,比如羅馬、粗體、斜體、粗斜體等。我們將這些字型分成2階段: 第一階段是羅馬字型,然後立即渲染人造粗體和斜體,最後(第二階段)用真實字型替代。這裡面還可以使用sessionStorage優化訪問重複檢視的場景。

十、CRITICAL FOFT

CRITICAL FOFT和標準的FOFI的唯一區別就在於第一階段羅馬字型的載入,CRITICAL FOFT不會載入羅馬字型的全集,只會載入它的一個子集(比如A-Za-z0-9),全集會在第二階段載入。

十一、CRITICAL FOFT WITH DATA URI

這個和CRITICAL FOFT的唯一區別就是羅馬子集字型的載入方式,前面是用Font Load API完成了,這裡會將馬子集字型硬編碼成BASE64 URI的形式載入。

十二、 CRITICAL FOFT WITH PRELOAD

這個同上面的唯一區別還是第一階段羅馬子集字型的載入方式,它採用的是preload的形式載入。

結論

總的字型載入的策略可以用這個圖總結如下:

CSS @font-face效能優化

參考文獻

本文主要翻譯自如下部落格文章

  1. https://www.zachleat.com/web/comprehensive-webfonts/#font-display
  2. https://dev.opera.com/articles/better-font-face/

《IVWEB 技術週刊》 震撼上線了,關注公眾號:IVWEB社群,每週定時推送優質文章。

相關文章