完美字体回退

谢天谢地,如今我们对字体加载有了更多控制。我们不再需要强迫用户等待字体加载完毕才能显示文本,我们可以随着字体加载而进行替换。但这种替换可能会带来视觉上的突兀,出现很多突然的切换和重排。不过我们可以解决这个问题!

在网页上加载自定义字体时,负责任的做法是确保@font-face声明使用属性font-display,将其设置为swapoptional之类的值。

@font-face {
  font-family: 'MyWebFont'; /* Define the custom font name */
  src:  url('myfont.woff2') format('woff2'); /* Define where the font can be downloaded */
  font-display: swap; /* Define how the browser behaves during download */
}

这样设置后,用户加载页面时,不会出现延迟,他们就能看到文本。这对性能来说很棒。但它也存在设计上的权衡,用户会看到 FOUT,即“未定义文本闪烁”。也就是说,他们会看到页面加载时使用了一种字体,然后字体加载完成,页面会将这种字体切换为新的字体,从而造成一些视觉干扰,可能还会出现一些重排。

这个技巧的目的是最大程度地减少这种干扰和重排!

这个技巧来自于Glen Maddern,他在Front End Center上发布了一个关于此技巧的视频,他将 Monica Dinculescu 的字体样式匹配工具与 Bram Stein 的Font Face Observer 库结合使用。

假设您从 Google Fonts 加载字体。这里我将使用Rubik,两种字重。

@import url("https://fonts.googleapis.com/css2?family=Rubik:wght@400;900&display=swap");

在该 URL 的末尾,默认情况下您会看到&display=swap,这就是它们确保@font-face声明中包含font-display: swap;的方式。

在连接速度较慢的情况下,以下是包含文本的简单页面加载方式

首先您会看到回退字体,然后自定义字体加载完成,您会看到字体切换为自定义字体。

看到切换了吗?请记住,从某种意义上说,这是好的,因为至少文本从一开始就是可见的。但这种切换过于强硬,感觉视觉上很突兀。

让我们解决这个问题。

使用字体样式匹配工具,我们可以将两种字体叠加在一起,看看 Rubik 和回退字体之间的差异。

请注意,这里我使用system-ui作为回退字体。您应该使用经典的“网络安全”字体作为回退字体,例如 Georgia、Times New Roman、Arial、Tahoma、Verdana 等。大多数计算机默认安装了这些字体,因此它们是安全的回退字体。

在我们的案例中,这两种字体的“x 高度”几乎完全相同(注意红色和黑色小写字母的高度)。如果它们不同,我们就需要调整字体大小和行高以匹配。但幸运的是,我们只需要调整一下字间距就能让它们非常接近。

将回退字体调整为使用letter-spacing: 0.55px;,让它们的大小非常接近!

现在,诀窍是在字体加载之前应用这种样式。因此,让我们将其设置为默认样式,然后使用一个 body 类告诉我们字体已加载,并移除修改。

body {
  font-family: "Rubik", system-ui, sans-serif;
  letter-spacing: 0.55px;
}
body.font-loaded {
  letter-spacing: 0px;
}

但如何获得font-loaded类呢?Font Face Observer 库使它变得非常容易,并且跨浏览器兼容。使用该库后,只需几行 JavaScript 就可以调整类。

const font = new FontFaceObserver("Rubik", {
  weight: 400
});

font.load().then(function() {
  document.body.classList.add("font-loaded");
});

现在看看字体加载体验变得多么平滑和不那么突兀了

这是一个非常棒的技巧!

以下是这个简短的演示

在测试时,如果您看不到切换发生,请检查您是否已在机器上安装了 Rubik 字体。或者您的网络可能太快了!开发人员工具可以帮助您降低网络连接速度以进行测试。

当您使用多种字体和多种字重的字体时,事情会变得更加复杂。您可以观察每个字体的加载情况,并在它们加载时调整不同的类,调整样式以确保重排尽可能少。