如何避免由 Web 字体引起的布局偏移

开发 前端
这篇文章我们将探索令人惊讶的复杂文本渲染世界,以及一些解决无样式文本闪烁的技术。

前言

一些布局上的完全加载前后的变化很容易解决:为动态元素预先分配正确的空间,在图像上使用宽度和高度属性,并优先考虑 HTML 文档中的可见元素。但是,导致布局偏移的还有一个难以解决的问题:无样式文本 (FOUT) 的闪烁。

这篇文章我们将探索令人惊讶的复杂文本渲染世界,以及一些解决无样式文本闪烁的技术。

为什么字体会导致布局变化?

意外的布局变化(页面内容在没有用户交互的情况下移动)不利于用户体验。下载网络字体时,当字体题发生变化时,会导致包含元素(例如<div>,段落或段落)的大小发生变化,从而导致布局发生变化。当 Web 字体的字体高度或段落长度与系统字体相比不同时,就会出现这种情况。布局页面时,浏览器将使用后备字体的尺寸和属性来确定包含元素的大小,即使你已声明 Web 字体以阻止系统字体font-display: block!

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

如何避免

我们现阶段的网页为了满足用户的审美往往会使用一些特殊字体,但与此同时也会带来一些体验上的问题,最常见的就是页面的加载速度以及文本闪烁等。所以我们有必要对字体进行一些优化操作来满足我们“日益挑剔”的用户。

font-display

最粗暴的解决方案是只需要一行CSS代码就能够解决。

font-display: optional;

为什么说只需要这一行代码就能够解决呢,因为如果 Web 字体在呈现文本时不可用(加上 100 毫秒),它会告诉浏览器使用备用系统字体。这意味着在未缓存的页面加载时,可能会使用备用字体,但所有后续页面加载都应使用 Web 字体呈现,因为它将被下载并在缓存中可用。

它一共有以下几个属性:

  • 「auto:」字体显示策略由用户代理
  • 「block:」为字体提供一个短暂的阻塞周期和无限的交换周期,在等待网络字体时隐藏文本最多三秒钟,并在加载时始终交换网络字体
  • 「swap:」为字体提供一个非常小的阻塞周期和无限的交换周期,尽快显示文本,并在加载时始终交换网络字体
  • 「fallback:」为字体提供一个非常小的阻塞周期和短暂的交换周期,隐藏文本最多 100 毫秒,然后仅在三秒内加载时交换网络字体
  • 「optional:」为字体提供一个非常小的阻塞周期,并且没有交换周期,隐藏文本最多 100 毫秒,然后仅使用可用的网络字体,从不交换

上面这样解释如果还不太明白的话,可以看看下面这张图:

「Optional 是唯一保证不发生布局偏移的字体显示值」

不幸的是,系统字体不一定是最好的设计,并且它们在各个操作系统之间并不一致。大多数设计师一想到向用户展示一个备用系统字体就会畏缩。接下来介绍各种优化以更快地将字体文件传送到浏览器,允许使用任何字体显示选项,但显示系统字体的风险最小,或者用于optional: 以外的选项而不触发布局转换。

优化字体文件

优化网络字体有两种关键方法:子集和格式。

子集字体

许多字体将具有来自多个字母的字形(字形是单个字符,例如a或&)如果你仅以拉丁字母 (a - Z) 提供并且不使用连字(如é),那么这些字形表示您的字体文件中浪费的字节。

从此字体中删除非拉丁字符会产生woff2一个大小为六分之一的文件。

字体格式

各主流设备基本都支持 woff2 字体格式,因此网站中没有必要再引入多种不同格式的字体了。一般地,建议只引入 woff2 就好了,既可以保持代码的简洁性,又可以减少上传到你服务器的文件。

加载更少的字体

虽然我们会将字体转换成woff2格式,但文件大小依然有好几百K,有时甚至是几M,字体文件的大小也会影响页面整体的渲染速度。有些时候我们只需要一些极少数的文字用于特殊字体,那我们就没必要将一整个字体文件引入了。

提取字体

当我们遇到上面这种情况时,千万不要将一整个字体包引入进去,这将极大地浪费网络带宽,从而影响页面的加载。这里推荐使用font-spider 字蛛来提取文字。

  • 安装font-spider
npm install font-spider -g
  • 提取

我们还是以上面那段诗句为例,那里我们用的是汉仪旗黑.woff2字体文件。这里还是经过缩小文字库之后的大概是32K

我们再在项目目录下执行以下命令

font-spider index.html

这时会生成一个.font-spider目录,并将提取后的字体文件放在该目录下。现在的字体文件大概就只有10K,比之前的体积小了好几倍。

使用系统字体

Web 字体很受欢迎,因为它们允许设计人员在浏览器中保持一致的外观和感觉。如果不需要,系统字体将是呈现文本的最快方法。如果当前的Web字体接近系统字体,您可以使用Monica的Font Style Matcher来调整字体设置,直到获得近乎完美的匹配。

使用系统字体意味着文本将尽可能早地呈现。我们现在还拥有使字体与操作系统匹配的方法,这可能比以前的备用选项(如 Arial 和 Helvetica)更具吸引力。为此,我们需要按特定顺序列出所有操作系统的系统字体:

body {
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
}

快速交付字体文件

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

使用CDN托管

一般来说,我们应该提供我们服务器中的字体以避免连接到第三方服务器的成本,这对于高延迟连接尤其重要。使用第三方服务意味着您的字体将被延迟。最好的情况是您直接从另一个主机名(例如fonts.gstatic.com)请求字体文件,这会产生连接成本——DNS 查找、TCP 连接和 TLS 协商。最坏的情况是多跳,例如从fonts.googleapis.com加载引用fonts.gstatic.com上的文件的 CSS 文件,会导致两次连接损失。所以我们一般会将一些字体文件等静态资源托管在我们自己的CDN服务器上,以此来加快资源的下载速度。

缓存字体

字体可以缓存在两个地方:「客户端和CDN」。客户端上的缓存对于会话中的导航很重要,并且应该以避免重新验证请求的方式完成。重新验证请求 (if-not-modified和if-modified-since) 将阻止浏览器使用字体文件,直到它验证它在服务器上没有更改。字体很少改变,所以我们应该如下实现一个缓存头,并在字体改变时更新文件名来破坏缓存:

cache-control: max-age=31536000,immutable

这告诉浏览器他们可以保留字体长达一年并且不需要重新验证( Firefox 和 Safariimmutable 支持,Chrome 应该自动避免重新验证请求)。避免将 ETag 添加到这些响应中,因为它们可能会强制重新验证。

还要检查您的 Content Delivery Network 配置是否可以将字体文件存储在缓存中,较旧的配置可能不包含.woff2扩展名,从而导致原始命中并减慢响应速度。

使用预加载

一般来讲浏览器不会随便地去下载字体文件,它们会等到渲染树构建完成后才能知道需要哪些字体。这意味着仅在浏览器下载并解析 HTML 和 CSS 时,即在呈现文本之前,才请求 Web 字体。「但需要注意的是,内联 CSS 不需要网络请求,这意味着我们的字体可以在页面加载的早期获取。」

渲染树的构建过程会阻塞Web字体的请求。

但是如果我们确定页面文本的渲染肯定会用到一些网络字体时,我们可以使用preload让浏览器提前下载字体文件。

<link rel="preload" href="./public/fonts/汉仪旗黑.woff2" crossorigin="anonymous" type="font/woff2">

当浏览器解析这行 HTML 时,它会立即发送一个对字体文件的高优先级请求。

「但是需要注意的是,预加载的请求会占用其它请求的带宽,所以我们在使用过程需要考虑清楚是否值得这么做。」

将字体转为Base64

还有一种常用的方法是将字体作为 Base64 字符串嵌入到 CSS 中,从而无需额外的字体请求并确保在呈现文本时字体可用。

但这个方法也不是绝对的好方法,它只适合一些小型字体文件,例如上面提到的使用font-spider提取后的字体文件,并且该字体文件足够小,因为将字体文件转化为Base64字符串往往会增加体积。

一般来讲它有以下缺点:

  1. 字体文件是压缩的二进制对象,编码为 Base64 字符串会显着增加大小。CSS 包的 gzip 或 brotli 压缩并不能完全弥补这种膨胀。
  2. 字体将被发送到每个浏览器,即使有的浏览器不能使用
  3. 字体很少更改,但 CSS 经常更改,这将降低字体的缓存效率,因为每次 CSS 更改都会使整个包无效
  4. 膨胀 CSS 大小几乎肯定会延迟页面渲染

使用f-mods减少布局偏移

F-mods 是对字体描述符规范的提议更新,其中包括四个新的描述符:

  • 「ascent-override (%)」 : 覆盖分配给上升器的大小
  • 「descent-override (%)」 : 覆盖分配给下降者的行高
  • 「line-gap-override (%)」 : 覆盖行间距
  • 「advance-override (#)」 : 为每个字符设置一个额外的提前量,以帮助匹配行宽并防止单词溢出

前三个都影响线的高度:线框高度 = 上升 + 下降 + 线间隙。基线位置 = 线框顶部 + 线间隙 / 2 + 上升。

这四个描述符的组合允许我们通过告诉浏览器在下载 Web 字体之前字符将占用多少空间来覆盖备用字体的布局以匹配 Web 字体。

f-mods 只真正修改垂直间距和定位。这意味着仍然需要处理字符间距和字母间距,否则可能会在不同的点出现断行的单词,从而导致元素高度发生变化,从而导致布局发生变化。但是@font-face 声明中没有letter-spacingandword-spacing属性,因此我们必须在主体或元素上声明。

@font-face {
font-family: custom-font;
src: url("./public/fonts/汉仪旗黑.woff2");
}
@font-face {
font-family: fallback-font;
src: local(Arial);
ascent-override: 100%;
descent-override: 20%;
line-gap-override: normal;
advance-override: 10;
}
/* 这些具体数值因字体而已,需要按照自己的字体进行计算调整*/
body {
font-family: custom-font, fallback-font;
}
.content {
letter-spacing: -1.1px;
word-spacing: -0.2px;
}

总结

总之,如果浏览器没有及时获取网络字体,并且可以应用font-display: optional到网络字体,让浏览器以备用系统字体呈现来防止布局偏移,否则的话就只能优化我们的字体以尝试在浏览器需要它们之前将它们获取到浏览器:

  • 使用woff2最小化文件大小
  • 优化字体文件
  • 加载更少的字体
  • 预加载关键字体
  • 在CDN上托管字体文件
  • 使用 f-mods 减少字体交换的影响
责任编辑:华轩 来源: 前端南玖
相关推荐

2018-04-10 13:02:51

HBase写入流程数据

2013-12-12 16:28:04

Lua脚本语言

2024-05-29 14:26:45

2022-11-03 16:10:29

groovyfullGC

2011-03-17 14:07:39

2022-10-27 21:32:34

oracle数据库

2011-03-17 14:21:35

2022-02-21 14:41:21

APIWeb安全

2012-07-12 10:13:35

2018-08-01 15:55:21

微软浏览器Windows

2014-08-13 15:55:17

Web响应式设计design

2016-04-08 09:24:01

脆弱代码更新

2012-03-31 10:12:55

CSSWEB

2024-07-03 09:13:26

SwiftUI修饰符框架

2018-05-24 13:58:03

LinuxGoogle Web字Font Finder

2021-08-02 13:08:56

高并发服务

2019-03-26 08:52:51

2015-03-15 18:47:09

2023-07-25 16:47:17

Serverless架构

2012-11-08 09:43:12

编程语言技术开发代码重构
点赞
收藏

51CTO技术栈公众号