關於@font-face載入前空白(FOIT)的解決方案

fi3ework發表於2018-02-03

『 文章首發於GitHub Blog

問題

先來看一下FOIT的表現:

1615b236f18d9e87

簡單來說FOIT(Flash of Invisible Text)就是文字使用了自定義的font-face,所以導致在自定義的字型載入完畢在之前,對應的文字會顯示一片空白。 在老版本的瀏覽器中,會優先顯示font-family中已經可以顯示的候選字型,然後當font-face的字型載入完畢後,再變成font-face的字型,這被稱作FOUT(Flash of Unstyled Text)。

下面是對FOUT和FOIT的一段英文解釋,比較全面:

Remember FOUT? When using a custom font via @font-face, browsers used to display a fallback font in the font stack until the custom one loaded. This created a "Flash of Unstyled Text" — which was unsettling and could cause layout shifts. We worked on techniques for fighting it, for instance, making the text invisible until the font was ready.

\A number of years ago, browsers started to shift their handling of this. They started to detect if text was set in a custom font that hasn't loaded yet, and made it invisible until the font did load (or X seconds had passed). That's FOIT: "Flash of Invisible Text". Should a font asset fail or take a long time, those X seconds are too long for people rightfully concerned about render time performance. At worst, this behavior can lead to permanently invisible content.

目的

在實際的工程中,我們可能有幾種對於font-face載入的需求:

  1. 恢復FOUT效果:在載入之前顯示空白並不一定真的比先使用fallback字型體驗更好,相比長時間的等待白屏,FOUT至少能讓使用者先看到內容。
  2. 在載入font-face字型完畢時能觸發鉤子函式:比如標題使用了font-face,標題一開始是隱藏的,直到font-face載入完畢再浮現,整個過程不希望發生FOUT,這就需要在font-face載入完畢時觸發鉤子函式來新增浮現效果的的CSS。

解決

第一個需求:使用font-display來實現FOUT

@font-face {
  font-family: Merriweather;
  src: url(/path/to/fonts/Merriweather.woff) format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: Merriweather,   /* Use this if it's immediately available, or swap to it if it gets downloaded within a few seconds */
               Georgia,        /* Use this if Merriweather isn't downloaded yet. */
               serif;          /* Bottom of the stack. System doesn't have Georgia. */
               
}
複製程式碼

有幾種不同的font-display,效果上會帶來微妙的差別

  • font-display: swap:瀏覽器會直接使用font-family中最先匹配到的已經能夠使用的字型,然後當font-family中更靠前的字型成功載入時,切換到更靠前的字型,相當於是FOUT。
  • font-display: fallback: 瀏覽器會先等待最靠前的字型載入,如果沒載入到就不顯示任何東西,這個過程大約持續100ms,然後按照順序顯示已經成功載入的字型。在此之後有大約3s的時間來提供切換到載入完畢的更靠前的字型。
  • font-display: optional: 瀏覽器會先等待最靠前的字型載入,如果沒載入到就不顯示任何東西,這個過程大約持續100ms,然後字型就不會再更改了(一般第一次開啟某頁面的時候都會使用fallabck字型,字型被下載但是沒被使用,之後開啟時會使用快取中的字型)。

第二個需求:使用Web Font Loader

使用JS而不是CSS來引入字型,WFL會在字型引入的整個過程提供多個鉤子函式,具體可以參考 官方文件Loading Web Fonts with the Web Font Loader

舉個小例子,比如我想讓某標題使用font-face字型。載入頁面後,標題一開始是不可見的,直到自定義字型被成功載入後標題才向上浮動顯示,當超過5s還沒成功載入字型時將按fallback字型顯示。這就需要判斷自定義字型什麼時候成功載入,什麼時候載入失敗。

function asyncCb(){
  WebFont.load({
    custom: {
      families: ['Oswald-Regular']
    },
    loading: function () {  //所有字型開始載入
      console.log('loading');
    },
    active: function () {  //所有字型已渲染
      fontLoaded();
    },
    inactive: function () { //字型預載入失敗,無效字型或瀏覽器不支援載入
      console.log('inactive: timeout');
      fontLoaded();
    },
    timeout: 5000 // Set the timeout to two seconds
  });
}
複製程式碼

Tips

還有一些關於font-face的知識我們也必須瞭解

  • font-face載入的時間:定義一個@font-face並不會立刻開始下載字型,而是在渲染樹生成之後,瀏覽器發現渲染樹中有非空的使用了font-face的字型才開始下載(IE9+會下載空節點)。
  • FOIT也有優點的時候,在顯示emoji表情時,某些emoji表情在預設字型下會是一個方框,現代瀏覽器預設的FOIT避免了fallback字型帶來的不可預測的顯示錯誤。
  • Chrome, Opera有一個預設的3s的強制顯示fallback字型的要求,如果在3s中無法正確載入font-face,會自動載入fallback字型。(但是Mobile Safari, Safari, Android Webkit沒有,也就是在font-face載入失敗時可能完全不顯示文字)

參考

相關文章