SERVICE PHONE

400-123-4657
  • 诚信为本,市场在变,诚信永远不变...

行业资讯

当前位置: 首页 > 富联动态 > 行业资讯

Web 字体优化 - 提升页面交互体验

发布时间:2024-05-20 点击量:95

Web 字体优化

本文是 网页字体度量及渲染 的下文。

本文将从字体加载、字体传输、字体渲染三个部分,介绍字体如何在对应生命周期内工作并给出优化方法。

先说结论:

字体优化其实就两种方案:

  1. 提高 web 字体加载 / 传输速度,在用户感知前加载完成
  2. 调整 web 字体或后备字体的渲染参数(Ascent / Descent /Line Gap),避免字体切换时出现布局偏移

Web 字体影响性能主要体现在两方面:

  1. 延迟文本渲染:在 web 字体完成加载前,浏览器会延迟文本渲染。这将影响 First Contentful Paint 首次内容绘制 (FCP)。有时也会影响 Largest Contentful Paint 最大内容绘制 (LCP)
  2. 布局偏移:浏览器切换字体时有可能造成布局偏移,进而影响 Cumulative Layout Shift 累积布局偏移 (CLS)

下载 web 字体时,当字体从后备字体切换为 web 字体时,会导致包含元素(例如)的大小发生变化,从而导致布局发生变化。当 web 字体的字体度量(Font Metrics)与后备字体相比不同时,就会出现这种情况。同时,布局页面时,浏览器将使用字体的尺寸和属性来确定包含元素的大小,即使你已声明。

注意:两种不同的字体是可能会导致布局发生变化的,但不是一定变化,这主要取决于字体的字体高度。

字体是网页典型的重要资源,没有字体可能导致页面白屏。因此,我们需要尽可能早的加载字体。

针对 CSS 内单独声明的,浏览器的字体加载可能有延迟。例如:


字体的延迟加载可能会延迟文本渲染。浏览器必须先构建依赖于 DOM 和 CSSOM 的 Render 树,然后才能知道它需要哪些字体资源来渲染文本。因此,字体会在其他关键资源请求之后延迟很长时间才开始请求,并且在获取字体资源之前浏览器可能阻塞文本渲染。

  1. 浏览器请求 HTML 文档。

  2. 浏览器开始解析 HTML 响应并构建 DOM。

  3. 浏览器发现 CSS、JS 和其他资源并调度请求。

  4. 浏览器在接收到所有 CSS 内容后构建 CSSOM,并将其与 DOM 树组合以构建 Render 树。

    1. 字体请求在 Render 树确定需要哪些字体来渲染页面上的指定文本后触发。
  5. 浏览器执行布局并将内容绘制到屏幕上。

    1. 如果字体尚不可用,浏览器可能不会渲染任何文本。
    2. 字体可用后,浏览器会渲染文本。

页面内容的第一次绘制与对字体资源的请求之间的“竞争”是造成“空白文本问题”的原因,浏览器可能会渲染页面布局但忽略任何文本。

注意,有一个误解就是只要在 内声明的 web 字体就一定会被浏览器下载。实际上,Render 树中只有真正用到的 web 字体才会下载,未用到的不会下载。下面代码示例中, 字体在 CSS 内的 和 中有声明,但 DOM 中并未使用到标签,因此字体不会被下载。

内联关键字体声明到 HTML head 标签中,而不是在单独的 CSS 文件中声明。这样可以让浏览器尽早发现字体声明而不是等到单独的 CSS 文件加载完成后才发现。



将放在 src 属性第一位,确保在本地已经安装了对应字体时不需要再进行网络请求。



如果知道页面肯定会用到的字体,那么可以利用资源优先级,使用 提前触发对 web 字体的请求,而无需等待创建 Render 树。通过预加载来防止布局偏移和不可见文本闪烁 (Flash of Invisible Text FOIT)

从 Chrome 83 开始,可以将 link rel="preload" 与 font-display: optional 组合来完全消除布局卡顿


属性会告诉浏览器将此资源作为字体下载,并帮助确定资源队列的优先级。

属性说明是否应使用 CORS 请求获取资源,因为字体可能来自不同的域。如果不设置此属性,浏览器将忽略预加载的字体。

字体预加载示例

字体无预加载示例


允许浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,建立与服务器的连接,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返延迟并为用户节省了时间。

  • FontFace:提供 JS 接口来定义和操作 CSS 字体、跟踪它们的下载进度,并覆盖它们的默认延迟加载行为,可以理解成下载字体的 fetch 方法。例如,如果确定需要特定的字体变体,可以定义它并告诉浏览器立即启动字体资源的获取。
  • FontFaceSet:消费 FontFace 下载的字体并查询字体下载状态。
属性 / 方法用途示例
查询字体加载是否完成
查询当前已添加的 FontFace 数目
查询当前 FontFaceSet 内添加的FontFace 状态
添加 FontFace 到 FontFaceSet
检查能否以特定字体渲染文本,可用来检测字体是否已加载
.........


更快的字体传输可以帮助字体更快渲染。如果字体传输足够快,那就可以避免布局偏移和 FOIT。

很明显,我们为了确保字体快速且正确地应用在我们网页上,我们必须让浏览器尽快下载我们的字体文件,在我们自己的 CDN 上托管字体将获得最佳性能。

WOFF 字体在 2012 年 12 月被 World Wide Web Consortium (W3C) 推荐使用,IE9+ 浏览器支持。WOFF 2 字体最早在在 2013 年 7 月 Chrome Canary 版本上可以使用,发展到现在,几乎已经成为自定义图标字体使用的标配,目前浏览器的兼容性已经相当不错了。

WOFF 2 标准在 WOFF 的基础上,进一步优化了体积压缩,带宽需求更少,同时可以在移动设备上快速解压。与 WOFF 中使用的 Flate 压缩相比,WOFF 2 是使用 Brotli 方法进行的压缩,压缩率更高,所以文件体积更小。

新的WOFF 2.0 Web 字体压缩格式平均要比WOFF 1.0小30%以上(某些情况可以达到50%+)

下面是一张 WOFF vs WOFF 2 字体大小对比图:

将字体作为 Base64 字符串嵌入到 CSS 中,从而无需额外的字体请求并确保在呈现文本时字体可用。但这个方法也不是绝对的好方法,它只适合一些小型字体文件,因为将字体文件转化为Base64字符串往往会增加体积。

定义每个资源支持的一组 Unicode 字符。这样就能将大型 Unicode 字体拆分成较小的子集(例如,中文、拉丁文和希腊文子集),并且仅需要在页面上下载呈现文本所需的字体集合。


Unicode 范围描述符可以指定通过逗号分隔的多个字符范围值,每个范围值都可以采用以下三种形式之一:

  • 单个代码点(例如,)
  • 区间范围(例如,):表示范围的开始和结束代码点
  • 通配符范围(例如,): 字符表示任何十六进制数字

一句话解释,将页面真正用到的文字打包成字体集,而不是使用字体全集。

字蛛(font-spider)

字蛛是一个智能 WebFont 压缩工具,它能自动分析出页面使用的 WebFont 并进行按需压缩。

百度 fontmin

第一个纯 JavaScript 字体子集化方案。

可以先阅读 网页字体度量及渲染 了解字体度量,帮助理解本章节。

字体显示时间线

字体显示时间线基于一个计时器,该计时器在用户代理尝试使用给定下载字体的那一刻开始。时间线分为三个时间段,在这三个时间段中指定使用字体的元素的渲染行为。

  • 字体阻塞周期

如果未加载字体,任何试图使用它的元素都必须渲染不可见的后备字体。如果在此期间字体已成功加载,则正常使用它。

  • 字体交换周期

如果未加载字体,任何尝试使用它的元素都必须呈现后备字体。如果在此期间字体已成功加载,则正常使用它。

  • 字体失败周期

如果未加载字体,用户代理将其视为导致正常字体回退的失败加载。

font-display 对应属性

以下表格中的阻塞时长是 W3C 提案中推荐值,各浏览器实现可能有差异。

属性阻塞时长交换时长介绍
auto浏览器默认浏览器默认字体显示策略由用户代理(浏览器各自默认行为)
block3s无限为字体提供一个短暂的阻塞周期和无限的交换周期。等待 web 字体时隐藏文本最多 3 秒, web 字体加载完成时交换。
swap100ms 或更少无限为字体提供一个非常小的阻塞周期和无限的交换周期。尽快显示文本, web 字体加载完成时交换。
fallback100ms 或更少3 秒为字体提供一个非常小的阻塞周期和短暂的交换周期。隐藏文本最多 100 毫秒, web 字体 3 秒内加载完成时交换,超过三秒保持展示后备字体。
optional文本初次渲染前的时长不阻塞文本渲染,并且没有交换周期。 web 字体在文本首次渲染前加载完成则展示 web 字体,否则展示后备字体,从不交换。

注意:下图是很多博文的配图,和 W3C 的规范 及浏览器实现是有出入的!!!!!!

不同的 策略需要在页面性能和样式之间进行平衡。因此,很难给出推荐的方法,因为它确实取决于个人偏好、web 字体对页面和品牌的重要性,以及字体延迟切换带来突兀的用户体验。

对于绝大多数页面,基本都适用于以下三种方案:

  1. 性能优先:使用 optional 是唯一保证不发生布局偏移的字体显示值。 文本渲染几乎无延迟。并且确保不会发生因为字体切换造成的布局偏移。但缺点也很明显,如果文本首次渲染前 web 字体未加载完成,后备字体可能样式并不满足 UI 要求。
  2. 需要文本尽快展示且需要确保使用 web 字体:使用 swap 确保了文本展示几乎无延迟,但可能由于 web 字体加载慢导致布局偏移。所以需要尽量让字体尽快加载完成。
  3. 确保文本展示使用 web 字体:使用 block 让文本有 3s 的不可见时间,web 字体基本可以在 3s 内加载完成,这样文本展示的时候就会自动使用 web 字体。但仍然存在由于 web 字体加载慢导致布局偏移的问题,同时文本 3s 的不可见时间也是一个负面影响。

CSS 描述符为与此字体关联的字形轮廓和指标定义乘数。这使得在以相同字体大小呈现时更容易协调不同字体的设计。

如何使用:


如下示例,结合 网页字体度量及渲染 文章内容,可发现 是等比放大了字体度量的所有参数,Ascent / Descent / Line Gap 均被放大 150%。

这个方案也存在很明显的问题,不同字体之间切换的 百分比需要手动调试计算。浏览器目前的兼容性也很一般。

使用 、 、 属性来调整字体。这 3 个 CSS 属性作用都是类似的,都是在 自定义字体中设置文字的上、中或下间隙大小。

W3C 文档 中的介绍并不明确,具体计算方式见下表。Ascent / Descent / Line Gap / em 的取值见之前写的文章:网页字体度量及渲染

属性描述介绍
ascent-override设置上悬线距离基线的距离normal默认值,由字体文件决定: (Ascent / em) * font-size。初始值为 Ascent / Em Size
<percentage>范围从 0% - ∞,值越大,文字位置越低。具体值大小为:percentage * font-size
descent-override设置下悬线距离基线的距离normal默认值,由字体文件决定:(Descent / em) * font-size。初始值为 Descent / Em Size
<percentage>范围从 0% - ∞,值越大,文字位置越高。具体值大小为:percentage * font-size
line-gap-override设置行间距> 行间距=行高 - 字体大小> 行高:line-height 为 normal 时的行高> 字体大小:每个字符的内容高度(不是font-size)normal默认值,由字体文件决定:(Line Gap / em) * font-size。初始值为 Line Gap / Em Size
<percentage>范围从 0% - ∞,值越大,行间隙越大。具体值大小为:percentage * font-size

下面以 Catamaran 字体为例(为了展示 Line Gap,此处手动将 Catamaran 字体的 HHead Line Gap 值从 0 调整为 500)。由下图可得知 Catamaran 字体的各个参数(重点关注 110%、54%、50% 这三个值):

  • Ascent: 1100
  • Descent: 540
  • Line Gap: 500
  • Em Size: 1000
  • ascent-override : normal=Ascent / Em Size= 110%
  • descent-override : normal=Descent / Em Size= 54%
  • line-gap-override : normal=Line Gap / Em Size= 50%

下面由几个示例来说明这三个 CSS 属性如何使用。示例中, 值为 normal ,红色文字为初始文字位置,蓝色文字为调整后文字位置,浅灰色为文本内容高度,深灰色为半行距(具体颜色说明见前文:网页字体度量及渲染)。

ascent-override

值越大文字行高越大。

示例 1:

由下图可见, 设为 0 时,文字的 Ascent 部分的高度完全没有了,行高塌陷了一大部分。但因为文字基线位置不变,所以相比于自己的内联行盒,文字整体朝上移动了。


示例 2:

由下图可见, 设为 110% 时,文字的 Ascent 部分恢复成了默认高度。


descent-override

值越大文字行高越大。

示例 1:

由下图可见, 设为 0 时,文字的 Descent 部分的高度完全没有了,行高塌陷了一大部分。但因为文字基线位置不变,所以相比于自己的内联行盒,文字整体朝下移动了。


示例 2:

由下图可见, 设为 54% 时,文字的 Descent 部分恢复成了默认高度。


line-gap-override

属性的作用是设置这个字体的行间距(行间距=行高 - 字体大小)。

设置要想生效,则对应字体所在的 属性值必须是 normal,无论是数值,长度值还是百分比值都会让 属性没有效果(只有当行高为 normal 时,最终的行高大小才由字体决定)。

值越大文字行高越大。

示例 1:

由下图可见, 设为 0 时,绿色文字的 Line Gap 部分的高度完全没有了,行高上下均塌陷了一部分。但因为文字基线位置不变,所以相比于自己的内联行盒,文字未移动。


示例 2:

由下图可见, 设为 50% 时,绿色文字的 Line Gap 部分恢复成了默认高度。


多字体实战

示例使用的两种字体对应的字体度量参数如下表。

属性AscentDescentLine GapEm SizeFont Size
Catamaran11005405001000100px
MILanPro104428201000100px

如下图为未使用任何 CSS 参数调整字体的示例,红色文字字体为 Catamaran,绿色文字字体为 MILanPro,绿色文字行高明显比红色文字小。如果 Catamaran 为 web 字体,MILanPro 为后备字体,那么在从 MILanPro 切换为 Catamaran 时,就会发生布局偏移。


将 MILanPro 字体 、、 分别调整为 Catamaran 字体的默认值,如下图为调整了CSS 参数后的渲染结果。虽然从字母 i (字母 i 上的点,红色为圆形,绿色为方形)可以明显看出两种字体渲染的文字样式的区别,但最终渲染文字的 Ascent / Descent / LineGap 尺寸完全一致。


注意,Ascent / Descent / LineGap 尺寸完全一致只是保证了两种字体的默认行高(line-height)完全一致,但不同字体相同字符的宽度不一定是一致的。下表为大写字母 X 在 Catamaran 和 MILanPro 字体中的宽度:

大写字母 X宽度FontForge 截图
Catamaran588
MILanPro691

如下图所示,红色为 Catamaran 字体,绿色为 MILanPro 字体,已经调整了 MILanPro 字体的度量参数,使两种字体的度量参数(Ascent / Descent / LineGap)完全一致。最终不同字体相同文本的宽度明显不一致。

字体优化其实就两种方案:

  1. 提高 web 字体加载 / 传输速度,在用户感知前加载完成
  2. 调整 web 字体或后备字体的渲染参数(Ascent / Descent /Line Gap),避免字体切换时出现布局偏移
  1. 快速加载:web.dev/fast/#
  2. MDN font-display: developer.mozilla.org/zh-CN/docs/…
  3. Preconnect: www.keycdn.com/support/pre…
  4. FontFace: developer.mozilla.org/en-US/docs/…
  5. FontFaceSet: developer.mozilla.org/en-US/docs/…
  6. How to avoid layout shifts caused by web fonts: simonhearne.com/2021/layout…
  7. Exploring x-height & the em square: fonts.google.com/knowledge/c…
  8. Deep dive CSS: font metrics, line-height and vertical-align: iamvdo.me/en/blog/css…
  9. 网页字体度量及渲染: juejin.cn/post/724214…

平台注册入口