写作

一款字体的性能预算

·6 min read

本页面借助 AI 辅助翻译。英文原文为权威版本。

本系列的上一篇主张许多应用根本不该加载任何字体。这一篇写给那些字体配得上其字节开销的界面——因为"值得加载"是工程的起点,而不是终点。Web 字体是页面上最重要内容的一项阻塞渲染的依赖。就按这个标准给它做预算。

一个文件,所有字重

杠杆率最高的单项决策是文件架构。传统字体家族以静态实例交付——常规体、意大利体、粗体,或许再加一个标题体——每个都是一次独立请求,加起来轻易就是 300–400KB。一个可变字体用一个文件承载同样的范围(往往更多:从 100 到 900 的每一档字重),而其体积通常小于两个静态文件。

一个 380KB 的四文件静态字体家族,对比一个承载同样范围、仅 92KB 的子集化可变字体。

文件更少也意味着部分加载的机会更少——不会出现粗体文件还在路上时"真斜体假粗体"齐飞的闪烁。而且可变字体的额外轴是功能性的:小字号的视觉字号轴、深色主题的字灰度(grade)轴——是能力,不是负担。

认真做子集化

大多数字体文件的大部分内容,是你的界面永远不会渲染的字符。子集化——把文件裁剪到你实际使用的文字系统和字符——通常能把一个拉丁字体减半;而 unicode-range 让你无需预先付费就能保留更广的覆盖:把拉丁子集和扩展子集声明为两个独立的字体面,浏览器只在匹配字符出现时才下载扩展部分。pyftsubset/fonttools 这类工具把这变成一个构建步骤,而不是手工活。WOFF2 无需多言;如果你在 2026 年还在交付其他格式,先从这里改起。

诚实地选择 font-display

加载行为是一个披着 CSS 外衣的产品决策:

  • block 在字体加载期间隐藏文本。几乎从不站得住脚——你是在拿读者的时间去换品牌的虚荣。
  • swap 立即显示后备字体,字体到达后切换。适合阅读型界面——但它把切换那一刻变成了你的问题,这正是下一节的主题。
  • optional 只在字体已被缓存时使用它(大致是:要么即刻,要么永不)。对于坚持要用 Web 字体的高性能应用,这是最诚实的设置——首次访问用后备字体阅读,之后每次访问都能看到品牌字体,且永远不会发生位移。
时间线上的三种 font-display 策略:block 隐藏文本,swap 先显示后备字体再发生位移,optional 从不位移。

把切换调校到隐形

如果你选了 swap,版面位移就归你消灭。现代工具链几乎能把这件事做到完美:基于本地系统字体构建一个后备 @font-face,用 size-adjustascent-overridedescent-override 调校到后备字体与 Web 字体占据完全相同的空间。文本在调校过的后备字体中即刻渲染;Web 字体到达;什么都不动。本站正是这样做的——标题字体的 Georgia 后备经过度量缩放,因此切换只是嗓音的变化,而不是版面的变化。

同一标称字号下的两款字体:x字高互不一致,直到 font-size-adjust 将后备字体归一。

最后以交付卫生收尾:自托管(第三方字体 CDN 既是一个隐私问题,也是一次你并不需要的连接建立开销),预加载那一个关键文件,并设置 immutable 缓存头,让成本每位用户一生只付一次。

按这套做法,一款严肃正文字体的完整预算是:一个预加载的可变 WOFF2,子集化到你的语言,约 40–90KB,swap 搭配度量兼容的后备字体,或者干脆 optional。这比大多数首屏大图都便宜——而这正是这款字体从一开始就必须达到的标准。