@font-face的困境

发布于 大漠

最近在互联风上有关于Web字体的性能表现讨论的越来越多。自定义的Web字体使用虽然还在持续增长,但是没有过多的人考虑其实际性能。如下图所示:

字体的大小和要求

如果你是一位网虫而又经常上网,特别是在低速的网速上,你可能经常看到这样的现象:

"字体加载延缓"

基本的@font-face使用方法至使用户加载字体受到阻塞。我去了解了@font-face以及寻找问题所在,并且探索改善字体加载性能,使使用Web字体的性能更好,加载字体更流畅。

我们要怎么达到目标

自从1998年IE4支持@font-face之后,在2008年3月至2010年3月,五大桌面浏览器(Safari、Firefox、Opera和Chrome)一个接一个实现@font-face功能——Web Fonts at the Crossing

2010开始,@font-face的使用得到爆炸性的增长。都是通过服务器加载字体,但是现在的网络与五年前是大大的不同:

  • @font-face的使用标准需要加载EOTTTFWOFFSVG格式字体,只有加载这些字体才能在主流浏览器渲染
  • 移动端的支持是后面的一个想法。例如,在2010年夏天iPhone4的出现,当不止一个字体的体积和样式加载时,运行Mobile Safari时会直接崩溃。

今天,[Caniuse.com]的数据统计,在美国@font-face在全球达到了92.92%的支持率,而在美国已达到了97.89%的支持率。

你通常只需要加载WOFF字体就能实现跨浏览器的支持。不幸的是,Web字体还有一个主要问题在早期没有得到妥善解决——性能

浏览器对@font-face的默认设置

In cases where textual content is loaded before downloadable fonts are available, user agents may render text as it would be rendered if downloadable font resources are not available or they may render text transparently with fallback fonts to avoid a flash of text using a fallback font. - //www.w3.org/TR/2011/WD-css3-fonts-20111004/#font-face-loading

虽然这部分规范直到2011年才真正的用到字体加载当中。在2009年的某个时候,Firefox和Opera开始支持@font-face时,文本先按后备字体渲染,直到字体资源下载完。许多用户的选择就很快被输出到客户端,而Flash却输出的是无样式的文(见Firefox bug).《fighting the @font-face FOUT》一文做了详细的描述。不久,大多数浏览器在字体还未下载之前会先隐藏文本。

不幸的是,@font-face现在的主要问题是要避免多年前的FOIT和Flash无样式文本。参考下面的示例:

@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 400;
  src: url(opensans.woff) format('woff');
}
 
body {
  font-family: 'Open Sans', 'Arial', sans-serif;
}

后备字体被指定,但许多浏览器要求文本应该保持透明,直到Open Sans下载或浏览器缓存中获取。许多Webkit浏览器之前会等待3s的延时和回退。对于网络慢的用户,有些浏览器可能要等待30s,这个时候没有样式的文本会一直呈现在用户的面前。

提高字体加载性能

移动用户在某些时候是网络接近3G的边缘,或者类似Jake Archibald的Lie-Fi网络服务,如机场和酒店无线网络,这几乎是接近一个拨号连接。我们要做的是修复加载@font-face不会受到阻塞。

虽然有些人可能会担心FOUT,性能的推迟通赤font-family自定义的字体,直到他们使用。这些仅仅是大多数用例这样使用,但可读内容胜过于自定义字体。

字体加载器

需要借且于JavaScript脚本代码,但这些小的变化可以让性能上有很大的差异。下面简单的概述了一些最好的选择:

Typekit/Google Web Font加载器,压缩后 11KB

您可能已经使用过这种加载器在某种程度上加载Google或Typekit托管的字体。Web字体加载器是用来比较两个页面的加载(查看代码)来确定Web字体是否可以使用。就像下面的示例,使用Web字体异步加载,避免阻塞页面:

<script>
  WebFontConfig = {
    typekit: { id: 'xxxxxx' }
  };
 
  (function(d) {
    var wf = d.createElement('script'), s = d.scripts[0];
    wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js';
    s.parentNode.insertBefore(wf, s);
  })(document);
</script>

Web字体加载器大多数是通过JavaScript来工作,提供回调,给body设置不同的类名。注:fvd字体变量描述

WebFontConfig = {
  loading: function() {},
  active: function() {},
  inactive: function() {},
  fontloading: function(familyName, fvd) {},
  fontactive: function(familyName, fvd) {},
  fontinactive: function(familyName, fvd) {}
};

.wf-loading
.wf-active
.wf-inactive
.wf-<familyname>-<fvd>-loading
.wf-<familyname>-<fvd>-active
.wf-<familyname>-<fvd>-inactive

Font Face Observer, 压缩后大小4KB

Font Face ObserverBram Stein中一个强大的,轻量级的选择。Bram在Typekit工作,并导致Web字体加载器阻塞,所以我只会在这里引用他:

Font Face Observer不一个小型的@font-face加载器和监控兼容Web字体的一个服务。它将监视Web字体运用于页面并通知你。它不会以任何方式限制你如何加载你的Web字体。不像Web字体加载器,Font Face Observer可以通过滚动事件检测字体加载效率和其最低开销。

有关于其更多的工作原理,可以阅读《Detecting System Fonts Without Flash》一文。

var observer = new FontFaceObserver('My Family', {
 weight: 400
});
 
observer.check().then(function () {
 console.log('Font is available');
}, function () {
 console.log('Font is not available');
});

如果你不介意写一些JavaScript脚本,那么Font Face Observer可以做任何Web字体加载器所做的事情。例如,下面的一个脚本示例,可以使用一个回调来调取服务器上的多个字体:

var fontFamilies = {
  'Open Sans': [
    {
      weight: 400
    },
    {
      weight: 700
    }
  ],
  'Open Sans Condensed': [
    {
      weight: 700
    }
  ]
}
 
var fontObservers = [];
 
Object.keys(fontFamilies).forEach(function(family) {
  fontObservers.push(fontFamilies[family].map(function(config) {
    return new FontFaceObserver(family, config).check()
  }));
});
 
Promise.all(fontObservers)
  .then(function() {
    document.body.classList.add('fonts-loaded');
  }, function() {
    console.log('Fonts not available');
  });

LocalFont

LocalFont是一个免费的服务,使用它可以容易的从本地加载字体和索引字体。该网站将字体文件转换为base64,并生成你需要的CSS和JavaScript。这种技术对于加载来说是绝对的快,但我无法找到任何有关于性能上的明确数据(有关于相关的描述可以点击这里)。你可能需要设计一个cache-busting做为更新之用。最终,我不建议使用localStorage,除非你有时间和资源去做彻底的测试。

拥抱FOUT

为了让这些字体加载能做一些策略工作,你需要计划让页面开始就能根据本地字体渲染。然后切换到下载后的Web字体。为了让这个过程不变得那么糟糕,我有几个建议:

  • 使用Font Family Reunion这样的服务,选择一个可备用的字体
  • 通过设置备用字体的font-sizeline-height等减少后备字体的流量。这样将能减少无样式文本的切找时间,特别是在文字页面中,会显得更快。相关介绍可以点击这里
  • 为超时设置一个自定义字体。如果字体加载仍然需要一定的时间,可能要一个简单的跳过用来避免触动开关。
  • Web字体缓存后,后面的页面就可以绕过字体加载的cookie或类似的技术检测。但是,在大多数现代设备的浏览器中,字体文件的缓存或本地存储检索是非常的快,你可能不需要担心这个问题。

未来的字体加载

很明显,这里还是有一些改进的余地。幸运的是,字体加载API和CSS字体渲染草案将会提供新的和改进字体加载控制。

字体加载API

新的字体加载API可以监听@font-face并删除现有的JavaScript加载器是很有必要。这已经在Chrome和Opera得到了实现。Bram Stein在Github上也开源了一个字体加载器——Font Loading polyfill

var f = new FontFace("newfont", "url(newfont.woff)", {});
f.load().then(function (loadedFace) {
  document.fonts.add(loadedFace);
  document.body.style.fontFamily = "newfont, serif";
});
 
document.fonts.ready().then(function() {
  // all @font-face families in use are ready
});

CSS字体渲染草案

CSS字体渲染允许开发人员只通过CSS来控制浏览器字体渲染行为。目前的规范是Fonts Level 4规范中的一部分,但它仍然为时过早,但将来是可用的:

@font-face {
  font-display: auto | block | swap | fallback | optional;
}

总结

标准的@font-face可能会损害网站的性能,但有很多方法实现Web字体加载而没有阻塞文本的渲染。而且工作是要解决这些问题也变得更为容易。

最后,我希望这篇文章能让你思考你的网站上如何加载字体。可以访问webpagetest.org 来检测您的网站。看到如何调整@font-face

本文根据@CHRIS MANNING的《The @font-face dilemma》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://viget.com/extend/the-font-face-dilemma