字体加载策略:@font-face 规则详解、字体格式声明、字体子集化

好,咱们直接进入正题。字体加载这件事,说简单也简单,说复杂……嗯,坑确实不少。我刚开始做前端那会儿,觉得用个自定义字体嘛,不就是写个 @font-face 就完事了?结果线上页面加载慢得像蜗牛,字体文件动不动就几 MB,用户等得直骂娘。

这一章,咱们就把 @font-face 的底裤扒干净。从规则怎么写、格式怎么选,到怎么给字体“瘦身”,一条龙讲透。

2.1 @font-face 规则详解

@font-face 是 CSS 里用来声明自定义字体的规则。说白了,就是告诉浏览器:“嘿,我这里有个字体文件,你拿去用吧。”

最基本的写法长这样:

@font-face {
  font-family: 'MyCustomFont';
  src: url('fonts/MyCustomFont.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

这里有几个关键属性,我挨个说一下。

2.1.1 font-family:给字体起个名

这个值就是你给字体取的名字。后面在 font-family 属性里引用它就行。注意,名字可以随便起,但别跟系统字体重名,否则会覆盖系统字体,引发奇怪的问题。

避坑指南: 我曾经在一个项目里把自定义字体命名为 'Arial',结果整个页面的 Arial 字体全被替换成了我的自定义字体,排版全乱了。后来我养成了习惯,自定义字体名都加个前缀,比如 'MyArial' 或 'CustomFont-xxx'。

2.1.2 src:告诉浏览器字体文件在哪

src 属性可以写多个值,浏览器会按顺序尝试加载。我个人习惯把最优的格式写在最前面,比如 woff2 优先,然后是 woff,最后是 truetype 或 opentype。

@font-face {
  font-family: 'MyFont';
  src: url('MyFont.woff2') format('woff2'),
       url('MyFont.woff') format('woff'),
       url('MyFont.ttf') format('truetype');
}

你想想看,浏览器拿到这个规则后,会先问自己:“我支持 woff2 吗?” 支持就用,不支持就往下看 woff,再不行就用 ttf。这叫“优雅降级”。

2.1.3 font-weight 和 font-style:匹配字重和样式

这两个属性用来声明这个字体文件对应的是哪个字重(粗细)和样式(斜体/正体)。

举个例子,如果你有四个字体文件:

  • MyFont-Regular.woff2 — 字重 400,正常样式
  • MyFont-Bold.woff2 — 字重 700,正常样式
  • MyFont-Italic.woff2 — 字重 400,斜体样式
  • MyFont-BoldItalic.woff2 — 字重 700,斜体样式

那你就得写四个 @font-face 块:

@font-face {
  font-family: 'MyFont';
  src: url('MyFont-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: 'MyFont';
  src: url('MyFont-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: 'MyFont';
  src: url('MyFont-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
}

@font-face {
  font-family: 'MyFont';
  src: url('MyFont-BoldItalic.woff2') format('woff2');
  font-weight: 700;
  font-style: italic;
}

这样,当你在 CSS 里写 font-weight: bold 或 font-style: italic 时,浏览器就能找到对应的字体文件。

小技巧: 如果你只有一个字体文件,但想让它同时支持多个字重,可以试试 font-weight: 100 900; 这种范围写法。不过,实际效果取决于字体文件本身是否包含多字重数据。大部分情况下,还是建议每个字重单独一个文件。

2.2 字体格式声明

字体格式这事儿,说白了就是浏览器怎么“读懂”你的字体文件。不同的格式,压缩率、兼容性都不一样。

目前主流的字体格式有这几种:

格式 扩展名 压缩率 浏览器支持 我的建议
WOFF2 .woff2 最高(比 WOFF 小 30%~50%) 现代浏览器全支持 首选,能上就上
WOFF .woff 较高 几乎所有浏览器 备选,兼容旧浏览器
TrueType / OpenType .ttf / .otf 无压缩或少量压缩 广泛支持 兜底方案,文件较大
EOT .eot 较低 仅 IE 老版本 如果你还在支持 IE8/9,可以考虑
SVG 字体 .svg 低 仅旧版 Safari / iOS 现在基本不用了

我个人习惯的声明顺序是:woff2 → woff → ttf。这样现代浏览器能拿到最小的文件,老浏览器也能兜底。

核心原则: 优先使用 WOFF2。它的压缩率最高,加载最快。如果字体文件本身不大(比如只包含几十个字符的子集字体),WOFF2 可能只有几 KB,加载几乎无感。

2.3 字体子集化

字体子集化,说白了就是“瘦身”。一个完整的字体文件可能包含几千个字符,但你的页面可能只用到了其中的几十个。比如你只用了英文和数字,却加载了一个包含中文、日文、韩文、拉丁文扩展区的字体文件,那大部分流量都浪费了。

子集化就是只保留你需要的字符,把不需要的统统删掉。这样文件体积能缩小 90% 以上。

2.3.1 怎么做子集化?

常用的工具有 glyphhanger、fonttools、subfont 等。我拿 glyphhanger 举个例子:

# 先分析页面用到了哪些字符
glyphhanger https://example.com

# 然后生成子集字体
glyphhanger --subset=MyFont.ttf --US_ASCII --formats=woff2,woff

这个命令会扫描页面,提取所有用到的字符,然后生成只包含这些字符的 WOFF2 和 WOFF 文件。

如果你不想用命令行,也可以在线工具,比如 Font Squirrel 的 Webfont Generator,或者 Google Fonts 的 ?text= 参数。

2.3.2 子集化的策略

我一般按这几种情况来:

  • 只包含拉丁字符: 用 --US_ASCII 或 --latin 参数,文件能小到 2~5 KB。
  • 包含常用标点: 加上 --latin-ext,文件大概 5~10 KB。
  • 中文场景: 中文子集化比较麻烦,因为常用字就有 3500 个左右。我建议只保留页面实际出现的汉字,或者按“常用 1000 字”来切。
注意: 子集化后,如果页面后续新增了字符,而字体文件里没有,浏览器会 fallback 到系统字体,导致样式不一致。所以,子集化最好在构建阶段自动完成,并且要确保覆盖所有可能用到的字符。

2.3.3 子集化后的 @font-face 写法

子集化后的字体文件,@font-face 写法完全一样。只是文件体积小了很多。

@font-face {
  font-family: 'MySubsetFont';
  src: url('MySubsetFont.woff2') format('woff2'),
       url('MySubsetFont.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

你看,代码没变,但加载速度天差地别。

2.4 本章知识体系图

下面这张图,把 @font-face 规则、字体格式、子集化之间的关系串起来了。你可以把它当作本章的“脑图”。

字体加载策略知识体系图 @font-face 规则 规则详解 font-family: 命名 src: 字体文件路径 font-weight / font-style 字体格式声明 WOFF2(首选) WOFF(备选) TTF/OTF(兜底) 字体子集化 glyphhanger 工具 只保留用到的字符 文件体积缩小 90%+ 核心流程 1. 确定需要的字符范围 → 2. 生成子集字体(WOFF2) 3. 编写 @font-face 规则 → 4. 在 CSS 中引用 最终效果:字体文件小、加载快、兼容性好

2.5 实战建议

好了,理论讲完了,咱们来点实际的。我总结了几条经验,你直接拿去用:

  1. 能上 WOFF2 就上 WOFF2。 别犹豫,这是目前最好的格式。
  2. 子集化是必选项,不是可选项。 哪怕你的字体只包含 26 个英文字母,子集化后也能小一半。
  3. @font-face 声明要完整。 font-weight、font-style、font-display 一个都不能少。
  4. 多个字重就写多个 @font-face 块。 别偷懒写在一个块里,浏览器会懵的。
  5. 构建阶段自动化子集化。 用 Webpack 插件或 Gulp 任务,别手动搞。
一个小彩蛋: 如果你用的是 Google Fonts,可以在 URL 后面加 &text=Hello 参数,只加载你需要的字符。比如 https://fonts.googleapis.com/css2?family=Roboto&text=Hello。这样加载的字体文件只有几 KB。嗯,Google 早就帮你想好了。

这一章的内容就到这里。记住,字体加载的优化,核心就是两件事:选对格式 和 只加载需要的字符。把这两点做好,你的页面加载速度至少能快 50%。