B08 字体与网络字体加载

2026-10-31 17:120 阅读约 12 分钟CSS

B08 字体与网络字体加载

摘要:字体由字体族栈、@font-face 声明与加载策略三部分组成。font-display 决定字体未就绪时显示什么,swap 会先显示回退字体再替换,代价是可观的布局跳动;可变字体用一份文件提供连续字重,适合替代多字重字体包。本文给出字体族栈的组织方式、@font-face 完整写法、四种 font-display 的取舍、度量覆盖减小跳动的做法与子集化策略。
关键词:字体族,@font-face,font-display,可变字体,FOUT

所属:frontend/css / B-进阶 | 上一篇:B07-背景渐变与滤镜 | 下一篇:C00-总结 | 返回:B00-总结

1 引言

引入网络字体后常见的三类问题:首屏文字长时间空白、字体加载完成时页面跳动、一个中文字体包动辄数兆。三者都由加载策略而非字体本身决定。

本文给出字体声明与加载的完整做法。范围见表 1-1。

项 本文覆盖 本文不覆盖
声明 @font-face、src、unicode-range 字体授权与商业使用
加载 font-display 四值、预加载、回退字体度量 服务端字体注入
组织 字体族栈、系统字体栈、可变字体 字体设计本身

表 1-1 本文覆盖范围

2 预备知识

项 要求 核对方式
环境 本地服务器可用 浏览器能打开 localhost
文件 css-practice/fonts.html 编码 UTF-8
浏览器 Chrome 130+,Network 面板可用 能限速观察字体加载
前置 A03 字号与行高已掌握

表 2-1 环境与前置要求

3 原理与实现

3.1 字体的两段加载

浏览器先用回退字体绘制,字体到达后替换,替换时机由 font-display 决定,如图 3-1 所示。

flowchart TD
    A["页面开始解析"] --> B["回退字体立即绘制"]
    B --> C{"font-display 取值"}
    C -->|block| D["最多隐藏约 3 秒"]
    C -->|swap| E["立即显示回退字体"]
    C -->|fallback| F["短阻塞后用回退字体"]
    C -->|optional| G["超时则本页不再替换"]
    D --> H["字体到达后替换 产生跳动"]
    E --> H
    F --> H
    G --> I["下一页才使用新字体"]

图 3-1 font-display 控制替换时机与阻塞时长

图上可见:四种取值只在「阻塞多久」与「是否替换」两个维度上不同;swap 换来的可读性与布局跳动是一对取舍。

3.2 完整实现

/* 运行环境:Chrome 130+;css-practice/fonts.css */

/* 1 系统字体栈:零加载成本,正文首选 */
:root {
  --font-sans: system-ui, -apple-system, "Segoe UI", "Noto Sans SC",
    "PingFang SC", "Microsoft YaHei", sans-serif;
  --font-mono: ui-monospace, "Cascadia Code", Consolas, monospace;
}

/* 2 网络字体:一份可变字体覆盖多个字重 */
@font-face {
  font-family: "Brand Sans";
  src: url("/fonts/brand-sans.woff2") format("woff2") tech(variations),
       url("/fonts/brand-sans-static.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2000-206F, U+3000-303F;
}

/* 3 回退字体度量覆盖:让替换时的跳动可控 */
@font-face {
  font-family: "Brand Sans Fallback";
  src: local("Arial");
  size-adjust: 97%;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}

body {
  font-family: "Brand Sans", "Brand Sans Fallback", var(--font-sans);
  font-synthesis-weight: none;      /* 无对应字重时不合成粗体 */
}

h1 {
  font-family: var(--font-sans);
  font-variation-settings: "wght" 700, "opsz" 48;
  font-optical-sizing: auto;
}

code { font-family: var(--font-mono); }

font-display 四值的取舍见表 3-1。

取值 阻塞期 替换行为 适用场景
auto 由浏览器决定 同 block 无明确要求时的默认
block 约 3 秒 到达后替换 图标字体、必须一致的标题
swap 极短 到达后替换 正文,可读性优先
fallback 约 100 毫秒 到达后替换,超时不再换 可减少跳动,适合正文
optional 约 100 毫秒 超时则本页不替换 弱网、装饰性字体

表 3-1 font-display 取值与阻塞、替换行为

中文字体体积控制见表 3-2。

手段 做法 效果
选 woff2 只提供 format("woff2") 比 woff 小约 30%
子集化 按 unicode-range 切片或按页打包 只下载用到的字符集
可变字体 一份文件覆盖 100~900 字重 取代 4~6 个静态字重文件
预加载 <link rel="preload" as="font" crossorigin> 提前发起下载,缩短阻塞
度量覆盖 size-adjust 等覆盖属性 降低替换时的布局跳动

表 3-2 五类体积与跳动控制手段

4 验证与讨论

  1. 在 Network 面板把网速限速到 Slow 3G,刷新页面,观察正文先用回退字体绘制再替换。
  2. 把 font-display 改为 block,确认首屏文字空白约 3 秒后出现。
  3. 改为 optional 并限速,确认本页不再替换,刷新后才使用网络字体。
  4. 注释掉 size-adjust 与 ascent-override,在替换瞬间观察标题位置的偏移量。
<!-- 预加载与跨域:字体必须带 crossorigin,否则二次下载 -->
<link
  rel="preload"
  href="/fonts/brand-sans.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
检查项 预期结果 说明
替换可观察 限速后可见回退字体先出现 图 3-1
阻塞差异 block 下首屏空白约 3 秒 表 3-1
预加载生效 字体请求出现在文档早期 3.2 节 link 代码
跳动可控 加度量覆盖后偏移显著减小 表 3-2

表 4-1 字体加载的四项验收

讨论与局限。 size-adjust 需要按回退字体实测调整,换回退字体就要重测。另,unicode-range 切片会增加请求数,字符集分散时反而更慢,需按站点实际字符分布评估。

5 常见问题

现象 原因 处理
首屏文字长时间空白 用了 block 且字体未预加载 改 swap 或加 preload
字体加载后整页跳动 回退与网络字体度量差异大 加 size-adjust 等度量覆盖
字体提示已下载但仍二次请求 预加载缺 crossorigin 补 crossorigin 属性
中文页面体积过大 全字符集打包 子集化或改用系统字体栈
可变字重不生效 font-weight 未声明区间 font-weight: 100 900
粗体看起来是被拉粗的 浏览器合成了字重 提供对应字重或设 font-synthesis-weight

表 5-1 字体加载的六类问题

6 结论

序号 结论 依据
1 font-display 在阻塞与替换间取舍 图 3-1
2 正文用 swap,装饰字体用 optional 表 3-1
3 预加载必须带 crossorigin 4 节代码
4 度量覆盖是降低跳动的直接手段 表 3-2

表 6-1 结论汇总

参考文献

[1] MDN Web Docs · @font-face,https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face(访问于 2026-10-31)
[2] web.dev · 网络字体最佳实践,https://web.dev/articles/font-best-practices(访问于 2026-10-31)

返回文章列表

评论 0

0/2000

还没有评论,快来抢沙发吧~