本文主要介紹字體加載優化的常用策略,大部分內容為引用和翻譯。
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所支持的字體可以有以下類型:
src參數帶不帶引號都可以,參數的格式不同含義也不盡相同,比如下面:
src: url(fonts/simple.woff);src: url(fonts/simple.woff);src: url(fonts/coll.otc#foo);src: url(fonts/coll.woff2#foo);src: url(fonts.svg#simple);
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,即使頁面元素沒有使用對應的字體,也會下載。
在其它瀏覽器中也不盡相同,
比如在Firefox 和 IE 9+ 中,遇到如下情況也會下載字體:
html
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';document.body.appendChild(el);el.innerHTML = 'Content.';三、 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 FOFTCRITICAL 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的形式加載。
結論總的字體加載的策略可以用這個圖總結如下:
參考文獻本文主要翻譯自如下博客文章