fi3ework / blog

📝

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关于@font-face加载前空白(FOIT)的解决方案

fi3ework opened this issue · comments

commented

问题

先来看一下 FOIT(Flash of Invisible Text) 的表现(GitHub 只会播放一次 GIF,拖到一个新窗口刷新可重放):

1615b236f18d9e87

简单来说 FOIT 就是文字使用了自定义的 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.

之所以从 FOUT 变成 FOIT,就是在字体变更的时候会因此 re-flow,而 re-flow 的代价是很大的,所以干脆就直接不显示,在 font-face 载入后再显示。

目的

在实际的工程中,我们可能有几种对于 font-face 加载的需求:

  1. 恢复 FOUT 效果:虽然会引起 re-flow,但是对于用户体验来说,在载入之前显示空白并不一定真的比先使用 fallback 字体更好。
  2. 在加载 font-face 字体完毕时能触发钩子函数:比如希望某使用 font-face 的标题能够浮现出现,所以需要再 font-face 加载完毕时触发钩子函数来添加浮现效果的的 CSS。

解决

1. 使用 font-display 来实现 FOUT

  • font-display: swap:浏览器会直接使用 font-family 中最先匹配到的已经能够使用的字体,然后当 font-family 中更靠前的字体成功载入时,切换到更靠前的字体,相当于是 FOUT。
  • font-display: fallback:浏览器会先等待最靠前的字体加载,如果没加载到就不显示任何东西,这个过程大约持续 100ms,然后按照顺序显示已经成功载入的字体。在此之后有大约 3s 的时间来提供切换到加载完毕的更靠前的字体。
  • font-display: optional:浏览器会先等待最靠前的字体加载,如果没加载到就不显示任何东西,这个过程大约持续 100ms,然后字体就不会再更改了(一般第一次打开某页面的时候都会使用 fallabck 字体,字体被下载但是没被使用,之后打开时会使用缓存中的字体)。

2. 使用 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 加载失败时可能完全不显示文字)

参考

Thx for your share :)