Google Fonts 和 font-display

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 提供适用于您旅程各个阶段的云产品。从 免费赠送的 200 美元积分 开始!

嘿!这整篇文章都是关于 2019 年 5 月之前的一段时间,当时 Google Fonts 并没有提供在不自行托管字体的情况下使用 font-display 的方法。

要将 font-display 与 Google Fonts 结合使用,您需要在 URL 中包含一个 URL 参数,例如 &display=swap,例如 https://fonts.googleapis.com/css?family=Open+Sans&display=swap。如果您现在正在从 Google Fonts 复制代码,那么这是默认设置,因此您会自动获得它,但是如果您有现有的 Google 字体 URL 徘徊,或者您想将其更改为 类似于 optional 的内容,您可能需要添加它。

Zach Leatherman 指出,我们仍然有一些 希望的事情,例如字体的稳定 URL,这样我们就可以在自己的 CSS 中链接字体,从而避免目前所需的双跳。


这是原始文章

font-display 描述符在 @font-face 块中真的很棒。它本身就能为提高网页字体加载的感知性能做出巨大贡献。加载网页字体是一件棘手的事情,拥有像这样工作得这么好的工具对网络来说意义重大。

这件事意义重大,以至于 Google 自家的 Pagespeed Insights / Lighthouse 会因为您没有使用它而惩罚您。具有讽刺意味的是,它们自己的 Google Fonts(毫无疑问是网络上最常用的自定义字体库)并没有提供任何使用 font-display 的方法。

由 Daniel Dudas 在此处总结

Google Developers 建议使用 Lighthouse -> Lighthouse 警告在加载字体时没有使用 font-display -> 网页使用 Google Fonts 的方式是在 Google Fonts 上建议的 -> Google Fonts 不支持 font-display -> 真是让人头疼。

本质上,我们开发者希望有一种方法可以在 Google 提供的 @font-face 块中获得 font-display,就像这样

@font-face {
  font-family: "Open Sans Regular";
  src: url("...");
  font-display: swap;
}

或者,某种同样容易且同样有效的替代方案。

查询参数似乎是一种可能性

当您使用 Google 字体时,它们会为您提供一个 URL,该 URL 会生成一个样式表并使字体生效。就像这样

https://fonts.googleapis.com/css?family=Roboto

它们还支持 URL 参数来执行各种操作,例如加粗

https://fonts.googleapis.com/css?family=Open+Sans:400,700

以及子集

http://fonts.googleapis.com/css?family=Creepster&text=TRICKS
https://fonts.googleapis.com/css?family=Open+Sans:400,700&subset=cyrillic

所以,为什么不…

http://fonts.googleapis.com/css?family=Creepster&display=swap

该项目的负责人 表示 缓存是这个问题(尽管有人反驳了,因为他们已经支持任意文本参数)。

添加查询参数会减少跨站点缓存命中率。如果我们最终为 font-display 获取一些东西,再加上一大堆用于可变字体的参数,那么这可能会给我们带来问题。

他们在主题的后面再次提到这一点,因此听起来我们不太可能很快获得查询参数,但我希望自己错了。

选项:下载并自行托管它们

所有 Google Fonts 都是开源的,因此我们可以获取它们的副本,以供我们想要使用的任何用途。

一旦字体文件自行托管并提供,我们实际上就是在编写 @font-face 块来自己链接它们,并且我们可以自由地包含我们想要的任何 font-display

选项:获取并更改

Robin Richtsfeld 提出了一种想法,即从 JavaScript 中运行一个 Ajax 调用来获取字体,然后更改响应以包含 font-display 并将其注入。

const loadFont = (url) => {
  // the 'fetch' equivalent has caching issues
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.onreadystatechange = () => {
    if (xhr.readyState == 4 && xhr.status == 200) {
      let css = xhr.responseText;
      css = css.replace(/}/g, 'font-display: swap; }');

      const head = document.getElementsByTagName('head')[0];
      const style = document.createElement('style');
      style.appendChild(document.createTextNode(css));
      head.appendChild(style);
    }
  };
  xhr.send();
}

loadFont('https://fonts.googleapis.com/css?family=Rammetto+One');

很聪明! 不过,我不确定这如何融入字体加载的世界。由于我们现在正在 JavaScript 中处理此字体的加载,因此加载性能与我们加载运行此代码的脚本的时间和方式相关联。如果我们要这样做,也许我们应该考虑使用官方的 webfontloader

选项:服务工作者

与上面类似,我们可以获取字体并对其进行更改,但可以在服务工作者级别执行此操作,以便我们可以将其缓存(也许效率更高)。Adam Lane 写了这篇

self.addEventListener("fetch", event => {
  event.respondWith(handleRequest(event))
});

async function handleRequest(event) {
  const response = await fetch(event.request);
  if (event.request.url.indexOf("https://fonts.googleapis.com/css") === 0 && response.status < 400) {
    // Assuming you have a specific cache name setup   
    const cache = await caches.open("google-fonts-stylesheets");
    const cacheResponse = await cache.match(event.request);
    if (cacheResponse) {
      return cacheResponse;
  }
  const css = await response.text();
  const patched = css.replace(/}/g, "font-display: swap; }");
  const newResponse = new Response(patched, {headers: response.headers});
  cache.put(event.request, newResponse.clone());
    return newResponse;
  }
  return response;
}

甚至 Google 也同意使用服务工作者来帮助 Google Fonts 是一个好主意。 Workbox(他们用于抽象服务工作者管理的库)使用 Google Fonts 作为主页上的第一个演示

// Cache the Google Fonts stylesheets with a stale while revalidate strategy.
workbox.routing.registerRoute(
  /^https:\/\/fonts\.googleapis\.com/,
  workbox.strategies.staleWhileRevalidate({
    cacheName: 'google-fonts-stylesheets',
  }),
);

// Cache the Google Fonts webfont files with a cache first strategy for 1 year.
workbox.routing.registerRoute(
  /^https:\/\/fonts\.gstatic\.com/,
  workbox.strategies.cacheFirst({
    cacheName: 'google-fonts-webfonts',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200],
      }),
      new workbox.expiration.Plugin({
        maxAgeSeconds: 60 * 60 * 24 * 365,
      }),
    ],
  }),
);

选项:Cloudflare Workers

Pier-Luc Gendreau 研究了 使用 Cloudflare Workers 来处理这个问题,然后跟进 使用 Cloudflare 和服务工作者增强 Google Fonts,显然性能更好。

它有一个仓库。

选项:等待 @font-feature-values

Google 可能对此拖延不决的原因之一(他们也说过同样的话)是,有一个名为 @font-feature-values 的新 CSS @rule,它专门为这种情况而设计。 这是规范:

此机制可用于为整个字体系列设置默认显示策略,并使开发人员能够为不受其直接控制的 @font-face 规则设置显示策略。例如,当第三方字体铸造厂提供字体时,开发人员无法控制 @font-face 规则,但仍然能够为提供的字体系列设置默认字体显示策略。能够为整个字体系列设置默认策略对于避免赎金字形效果(即不匹配的字体字形)也很有用,因为显示策略随后将应用于整个字体系列。

在这方面似乎 没有太大进展(只是一点点 进展),但这并不意味着等待它不是一件非常棒的事情。