Skip to content

25 字体:解决自定义字体加载问题

你好,我是三桥。

上一节课,我们探讨了优化首次内容绘制(FCP)的4种方法,还剩下三种方法没讲,分别是缩短关键请求资源深度、减少请求数量和传输大小以及确保文本在网页字体加载期间保持可见状态。

前面两个方法相对简单,我们略去不提,这节课我们专注解决字体的问题。

网页字体问题

自从现代浏览器和移动浏览器成为主流以来,Web页面上的字体问题成为前端同学们必须面对的问题之一。网页字体对于网页非常重要,它们决定了网页文本的外观和风格。

你可能遇到过一些问题,比如:

  • 设计师提供的设计稿要求使用超过8M的授权字体库文件。
  • 无法把字体包文件压缩到更小的文件,或是拆分成多个文件。
  • 使用常规的Web Font方案后,页面加载速度变慢。

面对这些问题,我们应该如何有效地解决呢?虽然解决方案很多,但我们首先需要了解网页字体的基本概念。

字体库的组成

无论是系统字体还是外部字体,字体库通常由多个字体集组成,包括字重、字形、字宽等变体。根据字体在网页中的显示效果,又可分为样式、粗细、大小等三种类型。

样式包括正常和斜体两种,粗细度也分为三种:正常、粗体、较轻。字体大小可以用不同的单位来表示,例如像素(px)、em和rem等。

理论上,一个完整的字体库应该包含两种样式和三种粗细度。这就意味着一个字至少有6种风格。如果要包括所有文字,那么文字组合数量就会非常多。

所以,大量的字符集和变体是中文字体包文件较大的一个原因。

特殊情况下,企业可能会购买专有的版权字体,并将其作为产品和品牌的重要元素。无论是App还是Web,使用自定义字体成为开发工作中不可忽视的部分。如果Web需要使用自定义字体,就需要利用Web Font来支持。

什么Web Font?

一个计算机基本常识是,软件显示文本依赖于操作系统的字体库。内置的字体库决定了可以显示哪些字体,比如Arial、Helvetica、Times New Roman等。

同理,Web浏览器也需要系统字体库来显示文字内容。

默认情况下,网页可以定义多个系统字体库,浏览器按顺序找出可用字体进行显示。网页还可以引入外部网页字体,当浏览器解析网页时,如果发现了外部字体,会根据设定的策略下载并应用在网页上,这就是Web Font。

Web Font不是一个独立的标准,而是专门设计用于网页显示的字体,可以提供良好的视觉效果和用户体验。与传统不同,Web Font的设计需要考虑到网络环境、跨平台兼容性、加载速度、可访问性等因素。

要想实现网页上显示第三方字体,就要遵循字体格式标准、字符编码标准以及CSS规范。包括有:

  1. @font-face规则。这是CSS中定义如何在网页中引入第三方字体的方法。
  2. 字体压缩格式。现在支持网页的字体格式包括TTF、OTF、WOFF、WOFF2等4种格式,其中WOFF和WOFF2是用于在Web上传输字体的压缩格式。
  3. Unicode字符编码。Unicode是一种字符编码标准,用于表示世界上几乎所有的文字和符号。
  4. 加载和渲染规范。CSS Font Module Level 3是W3C制定的规范,它定义了网页字体加载和渲染的规范,包括如何定义属性、加载顺序、匹配规则等内容。
  5. 网页字体加载器。网页字体加载器(Web Font Loader)是一个JavaScript库,可以让程序更好地控制字体加载。

总的来说,要实现自定义网页字体的同时,还要考虑加载速度和用户体验,需要结合多个技术领域才能很好地完成。

网页字体对全链路的影响

网页字体文件的大小是影响前端全链路性能的主要因素之一。除系统默认字体外,第三方外部字体需要通过网络请求后,才能正常显示。

由于字体文件较大,如果在FCP指标范围内下载第三方字体文件,无疑会增加页面加载时间,甚至使得白屏时间更长。尤其是网络慢时,字体文件下载和解析时间更长。

不同字体库的设计差异很大,包括风格、大小、间距等。如果默认先显示系统默认字体,等到字体文件加载完成后再显示新字体,会导致浏览器重新排版和渲染,影响CLS指标。

因此,我们无法控制字体文件大小,但我们可以通过如优化文件下载等多种策略优化来解决网页字体引起的用户体验问题。现在,让我们来探讨有哪些优化策略。

优化方案

关于解决网页字体加载问题,我提出了6种优化方案的建议,现在我们逐一进行讨论。

方案1:避免使用多个外部第三方字体

我曾经负责过一个项目,网页引用了两个字体文件。每个字体文件对应着两种粒度,即正常和粗体,每个文件的大小约8M,完全下载需要至少60秒。

从这个例子显示,加载一个第三方字体文件已经很耗时,如果加载更多的字体文件,时间会更长。

虽然font-family属性允许设置多个备用字体,但不能为了设计需求随意增加网页字体文件,还是需要考虑增加后对用户体验的影响。因此,最佳的方案是, 使用一套第三方网页字体,并使用系统默认字体作为备选字体。

这里特别提示一下,不建议使用以下的代码写法。

body {
    // 不建议在body和p使用两套Web字体
  font-family: 'Roboto', sans-serif;
}

p {
    font-family: 'zhengyuan-55', sans-serif;
}

@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  src: url('Roboto-Regular.woff2') format('woff2');
}

@font-face {
  font-family: 'zhengyuan-55';
  src: url('zh_HANS_HANT.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
}

方案2:优先使用WOFF2格式文件

虽然支持网页的字体格式有4种,但实际上,只有WOFF2格式是最有用的,主要有6个原因。

  • 与WOFF2相比,EOT、TTF 和 WOFF三种文件都大。
  • WOFF和WOFF2都有内置压缩功能,WOFF2是WOFF的升级版本,可以提高30%的压缩效果。
  • WOFF和WOFF2.0都兼容新旧版浏览器。
  • EOT格式主要是为IE浏览器设计,它对其它浏览器的支持度不如WOFF和WOFF2。
  • TTF格式文件未经过压缩和加密,在网络传输时可能有安全问题。
  • WOFF是Web字体标准之一,并得到了W3C的支持和推荐。要支持IE浏览器,建议使用WOFF而不是EOT。

随着技术的进步,现代Web浏览器已成为主流,WOFF2是Web字体格式的最佳首选。除非你的前端项目中还需要兼容非常旧的浏览器,否则使用WOFF2足够。

方案3:子集内嵌

一个中文网页字体库可以包含所有的中文字体,根据现代汉语常用字表统计有3500个字。如果还要加上繁体字,那就有4808个字。

如果一个字以0.5K来算,3500个字就至少1700K,这还不包括繁体字符、英文字符和符号等。

实际上,每个页面用到的字体数量远远不到3500个。即使是一篇长文章,也有很多重复的字。记录不需要的字体只会浪费流量,甚至降低用户体验。

另一个问题是,虽然网页字体可以人工干预删除不必要的字符,但真实的情况是我们无法提前知道每个页面会使用哪些中文字符。

总结来说,我们面临三个问题:中文字多,加载无用字符、无法预判页面字符。有一种方法可以解决部分的问题,它就是 unicode-range 属性。

unicode-range 属性是CSS提供给字体定义的字体子集,允许我们选择字符范围并告诉浏览器使用哪些字符。 这样,浏览器可以根据字体子集范围下载少量字符,解决了下载较大文件慢的问题。

unicode-range 的属性值使用Unicode字符编码定义的字符范围,它可以支持单个字符编码、编码区间,通配符区间、多个区间值等。

例如,如果前端页面只需要显示中文、英文和常用符号字体,那么我们只需要定义三种字体范围就可以下载部分字体。比如参考下面的代码。

@font-face {
  font-family: 'zhengyuan-55';
  src: url('zh_HANS_HANT.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  unicode-range: U+4E00-9FA5,U+30-39,U+61-7a,U+41-5a
}

在代码中,我们可以看到定义了四种字符范围,包括:汉字 4E00-9FA5,数字 30-39,小写字母 61-7a,大写字母 41-5a

不过这个方案并不完美,无论怎么定义字符范围,最终还是下载很多字符,并没有完全解决字体文件过大的问题。那还有什么解决方案吗?继续往下看。

方案4:使用src的local(),读取已安装字体

有一种需求场景,就是前端页面要保持和App一致的字体格式。这种情况我们可以让App读取本地字体库,这样就无需网络请求。实现方案是这样的。

首先,安装App时,要内置网页字体文件,并实现允许网页加载本地字体文件。

其次,使用CSS提供的local()方法加载本地字体。

具体用法,请参考下面的CSS代码。

@font-face {
  font-family: 'Awesome Font';
  font-style: italic;
  font-weight: 400;
  src: local('Awesome Font Italic'),
       url('/fonts/awesome-i.woff2') format('woff2'),
       url('/fonts/awesome-i.woff') format('woff');
}

local()指令的作用是引用、加载和使用安装在本地的字体。通常,如果App内置了Web字体库,此函数会直接加载本地字体,不需要网络请求。如果找不到,会加载第二种字体。

所以,第4个方案就是加载已安装的本地字体文件。

实际上,用户很少在本地安装第三方字体,尤其是在移动设备上。所以,使用local()的方案主要目的是让前端页面兼容已安装第三方字体的用户,比如App。

方案5:优先使用系统默认字体

有没有注意到,即使采用WOFF2方案,字体子集方案、local()方案,仍然无法避免字体文件过大和加载时间过长的问题。

此外,由于自定义字体文件大,加载需要时间,这就造成了一些浏览器在字体加载前隐藏文本,导致了不可见文本闪烁(FOIT)的问题。

解决闪烁问题有两种方法。

第一种是 font-display 的显示策略。

font-display 属性用于指定 font-face 在样式中的显示策略,总共有5种策略,分别是auto、block、swap、fallback、optional。

如果优先显示自定义字体,可以使用block策略。简单来说,这就是让浏览器尽早提供网页字体,尽可能减少文本显示延迟。但通常情况下,我们并不推荐使用这种策略。

swap、optional、fallback三个值在自定义字体还未准备好时,让浏览器使用系统默认字体。

这些策略可能会导致布局偏移,影响CLS。如果使用了optional,可以配合预加载来减轻字体加载对CLS的影响。

如果优先保证文本字体的显示,然后再显示自定义字体,就需要使用swap策略,他对CLS的影响是最低的。

具体的代码参考如下。

@font-face {
  font-family: 'Awesome Font';
  font-style: italic;
  font-weight: 400;
  src: local('Awesome Font Italic'),
       url('/fonts/awesome-i.woff2') format('woff2'),
       url('/fonts/awesome-i.woff') format('woff');
  font-display: swap;
}

无论使用swap策略,还是optional+预加载策略,都要根据页面的实际情况来决定哪个方案最优。

下面的小视频展示了使用swap策略后,页面出现闪烁的效果。

第二是利用CSS字体加载器(CSS Font Loading API)。CSS字体加载器的主要功能是通过JavaScript代码来控制字体的下载时机。

这个方案需要一定的代码逻辑。首先创建FontFace对象实例,然后通过load方法加载字体,并通过监听下载状态,如果下载完成,就可以在事件内完成字体的更换操作。

下面是官方提供的代码示例。

// 创建字体对象实例
const font = new FontFace("myfont", "url(awesome-i.woff2)");

// 把字体对象加入到文档字体库中
document.fonts.add(font);

// 加载字体
font.load();

// 等待自定义字体加载完成后回调函数
document.fonts.ready.then(() => {
  // 处理新字体逻辑,例如变更元素的样式名
});

从代码来看,使用CSS字体加载器来加载网页字体,逻辑比较易懂,具体细节就不再详细讲解。

需要注意的是,无论是通过CSS控制字体文件下载,还是通过JavaScript控制,最终可能会出现布局偏移等问题。

这就需要产品和设计师一起沟通协商和决策,如果布局偏移在可接受范围内,那么就可以忽略闪烁效果的问题。

方案6:用户降级,低端网络用户不使用自定义字体

综合前面五种优化方案,它们都是通过一定的策略来解决加载字体文件。这次我们另辟蹊径,采用用户降级的方案。因为在我看来,接入网页字体是一项重要但非必需的功能之一,主要有三个原因。

首先,第三方网页字体主要服务于品牌识别,而并非网页的核心功能。

其次,使用第三方网页字体并不影响用户对网页内容的理解和操作。

最后,对于网络环境较差的用户,加载字体文件可能会导致页面加载速度慢,影响用户体验。

因此,用户降级也是一个不错的方法。在特定环境(如低速网络)中,我们可以只使用系统默认字体,以减少加载时间,提高用户体验。

采用用户降级方案需要沟通和决策的,尤其是品牌、产品、设计三个不同团队的决策。无论选择哪种方案,都需要在品牌效应和用户体验之间找到平衡。

总结

总结一下,这节课我们重点探讨了如何优化加载第三方网页字体。虽然我们无法快速解决网页字体文件大小的问题,但可以通过多种的方案和策略,缩短用户加载时间。

优化第三方网页字体是前端全链路中非常关键的资源优化。FCP、CLS、LCP等核心指标都可能因优化策略而改变。所以,最好的优化方式之一是通过A/B测试来看哪种策略对业务更有价值。

总的来说,除非产品或品牌需要,否则不建议前端项目使用第三方网页字体。

下节课,我们将继续探讨全链路环节中另一个指标:LCP指标优化。

思考题

现在,我给你布置一道思考题。

尝试拓展一下思维,既然我们不能改变中文字体库的大小,那么是否可以根据页面内容的变化,动态地调整网页字体文件的大小?如果可以,我们应该如何操作呢?

欢迎你在留言区和我交流。如果觉得有所收获,也可以把课程分享给更多的朋友一起学习。我们下节课见!