惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
A
About on SuperTechFans
量子位
Engineering at Meta
Engineering at Meta
B
Blog
The Cloudflare Blog
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
J
Java Code Geeks
D
DataBreaches.Net
aimingoo的专栏
aimingoo的专栏
T
Tailwind CSS Blog
H
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell

皓子的小站

网站字体应用之坑——font-family 篇 糟糕的 meta name="theme-color" How to Automatically Track Newly Supported ESLint Rules in Oxlint How to Gradually Migrate from ESLint to Oxlint (Without Breaking Everything) 静态资源预压缩:零运行时开销,极致节省带宽 CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash Halo CMS Comment Backend 自定义网页鼠标指针——一段曲折的旅程 CVE-2025-70886 漏洞复现/PoC | 一个脚本让 Halo CMS 评论后台瘫痪 2025 年终总结 & 博客两周年 老用户专享!已有 Halo 专业版授权可免费升级商业版 自动追踪 Oxlint 对 ESLint 规则的新增支持 Halo 贡献者证书与实体周边盲盒开箱 博客评论系统指南 CDN 回源跟随配置导致登录异常问题排查 SCDN 免费赞助计划:助力高质量博客&博客圈 锐捷校园网:网络共享与带宽叠加方案(哈理工案例) ESLint 到 Oxlint 渐进式迁移快速上手指南 Fixing Vite Breaking Inline JS & CSS in Thymeleaf Templates 解决 Vite 破坏 Thymeleaf 模板内联 JS & CSS 的方法 我的博客,为什么是月更? 博客俱乐部一周年纪念品开箱 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验 题解分享:[AtCoder Beginner Contest 414 E] Count A%B=C 题解分享:[蓝桥杯 2025 国 Python A] 杨辉三角 P12876 FiF口语训练破解刷分教程(适用于 Windows) 不要与蠢人辩论 小心网络地雷·续篇 当心网络组织“开往”·续篇 当心网络组织“开往” 小心网络地雷
网站字体加载之&...
HowieHz · 2025-08-26 · via 皓子的小站

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

定位问题

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

  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

补充

考虑到声明 format 的优势,已补充相关声明。

同时参考 RFC 8081: The "font" Top-Level Media TypeMedia Types 重新定义了 preload 中 MIME声明。

原有的模板代码已改为如下形式(经过简化):

<th:block
  th:with="font=${theme.config?.styles?.custom_font_files},
          ext=${font.substring(font.lastIndexOf('.') + 1)},
          format=${ext == 'woff2' ? 'woff2' : 
            (ext == 'woff' ? 'woff' : 
            (ext == 'ttf' ? 'truetype' : 
            (ext == 'otf' ? 'opentype' : 
            (ext == 'eot' ? 'embedded-opentype' : 
            ((ext == 'ttc' or ext == 'otc') ? 'collection' : ext)))))},
          mime=${ext == 'woff2' ? 'font/woff2' : 
            (ext == 'woff' ? 'font/woff' : 
            (ext == 'ttf' ? 'font/ttf' : 
            (ext == 'otf' ? 'font/otf' : 
            (ext == 'sfnt' ? 'font/sfnt' : 
            (ext == 'eot' ? 'application/vnd.ms-fontobject' : 
            ((ext == 'ttc' or ext == 'otc') ? 'font/collection' : 'application/octet-stream'))))))}"
>
  <link rel="preload" th:href="${font}" as="font" th:type="${mime}" 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("[(${format})]");
    }
    :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>