本站在允许 JavaScript 运行的环境下浏览效果更佳

网站字体加载之坑

今天一位博客朋友向我反映:在使用我站点主题后不能正常加载自定义字体。

定位问题

他已经预先进行了一些排查,但我也要从头检查一遍。
我的检查顺序为:

  1. 字体是否正常加载(网络是否正常)。
  2. 字体内容是否损坏。
  3. @font-face 中定义的 srcurl 是否正确。
  4. @font-face 中定义的 font-display 是否正确。
  5. @font-face 中定义的 font-family 和实际使用的 font-family 是否对应。

我抵达他的站点,打开开发人员工具(F12),发现控制台提示 "The resource xxxxx.ttf was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate as value and it is preloaded intentionally."。显然字体正常预加载了,但是未能正常使用。

接下来我下载了他的字体,在本地预览发现没有问题,排除了字体损坏的可能。之后我检查了 @font-face 中定义的 srcurl,对比预加载的链接,是保持一致且正确的。

确认 font-display 设置为 swap 值。有无限的交换周期,即使加载缓慢也不会影响字体显示。

最后我使用 Ctrl + Shift+ C 随便点击一个文字,进入开发人员工具的“元素”子页面,点击下方的“已计算”,搜索 "font-family"。对比后确认正确无误。与此同时,下方的“呈现的字体”这一栏说明了并没有正确加载字体。

我打开本地测试环境,经过对照实验后,发现 woff2/woff 格式能够正常显示,而 ttf 格式的字体无法正常显示。

发现问题

我从浏览器历史记录中找出 MDN 的 font-display 文档,它给的例子是:

@ font-face {
  font-family: ExampleFont;
  src: url(/path/to/fonts/examplefont.woff)format('woff'),
       url(/path/to/fonts/examplefont.eot)format('eot');
  font-weight: 400;
  font-style: normal;
  font-display: fallback;
}

其中声明了 eot 格式和 woff 格式的字体的 format 分别为 'eot' 和 'woff'。@font-face 多了一个空格并且使用了全角括号和全角逗号,应该是笔误。

我查看其英文原始页面 font-display

它给的例子是:

@font-face {
  font-family: ExampleFont;
  src:
    url("/path/to/fonts/example-font.woff") format("woff"),
    url("/path/to/fonts/example-font.eot") format("eot");
  font-weight: 400;
  font-style: normal;
  font-display: fallback;
}

我隐隐约约觉得哪里不对劲,但是说不上来。

再次检查主题代代码(以下是经过简化的相关主题代码):

<th:block
th:with="font=${theme.config?.styles?.custom_font_files)}"
>
  <link
    rel="preload"
    th:href="${font}"
    as="font"
    th:type="'font/' + ${font.substring(font.lastIndexOf('.') + 1)}"
    crossorigin
  />
  <style th:inline="css">
    @font-face {
      font-display: swap;
      font-family: "custom";
      font-style: normal;
      font-weight: 400;
      src:
        local("[(${theme.config?.styles?.custom_font_name})]"),
        url("[(${font})]") format("[(${font.substring(font.lastIndexOf('.') + 1)})]");
    }
    :root {
      --higan-font-family:
        custom, ui-sans-serif, system-ui, -apple-system, blinkmacsystemfont, segoe ui, roboto, helvetica neue,
        arial, noto sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", segoe ui symbol, "Noto Color Emoji" !important;
    }
  </style>
</th:block>

忽然,发现我先入为主的认为 format 的值就是字体文件后缀。

我找出 MDN 在这方面的相关文档:src

关键字 字体格式 常见扩展名
collection OpenType Collection .otc、.ttc
embedded-opentype Embedded OpenType .eot
opentype OpenType .otf、.ttf
svg SVG Font(已弃用) .svg、.svgz
truetype TrueType .ttf
woff WOFF 1.0 .woff
woff2 WOFF 2.0 .woff2

恍然大悟,原来是 format 的值写错了!

解决问题

事实上,format 是可以省略的。MDN 对 format说明

紧跟 url() 值的可选声明,为用户代理提供有关字体格式的提示。如果该值不被支持或无效,浏览器可能不会下载该资源,从而可能节省带宽。如果省略,浏览器将下载资源,然后检测格式。如果为了向后兼容而包含了已定义关键字列表中没有的字体源,请将格式字符串用引号括起来。下面的字体格式部分介绍了可能的值。

主题暂时不支持同时加载多个格式的字体,为快速解决问题,直接省略掉 format 值。同时我使用 woff2_compress 命令行工具和 sfnt2woff 命令行工具,将那位博友的字体转换为 woff/woff2 格式,帮助其快速上线。

关于 MDN 的错误,对原仓库和翻译仓库分别提 PR 以修复:mdn/content#40890mdn/translated-content#28578


0
上一篇 中文博客圈列表
下一篇 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验