fi3ework / blog

📝
861 stars 51 forks source link

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

Open fi3ework opened 6 years ago

fi3ework commented 6 years ago

问题

先来看一下 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

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 的知识我们也必须了解

参考

GreenMelon commented 5 years ago

Thx for your share :)