外部字体加载慢的核心原因是浏览器默认阻塞渲染等待字体就绪,而非@font-face写法错误;应采用font-display: optional+preload+子集化+显式尺寸约束的组合策略,避免FOIT/FOUT。

外部字体加载慢,不是因为 @font-face 写得不对,而是浏览器默认会阻塞文本渲染,等字体就绪或超时(通常 3 秒)才显示文字——这期间出现空白(FOIT)或闪动(FOUT),用户感知就是“慢”。关键不在“等不等”,而在“怎么等、等谁、等多久”。
font-display: swap 是基础,但不能单独用
设成 font-display: swap 确实能让浏览器立刻用系统字体渲染,等自定义字体加载完再替换,避免白屏。但它只解决“不空白”,不解决“替换后跳”:
- 中文字体文件大(全量思源黑体 >8MB),替换时字宽/行高突变,触发 layout shift,尤其在 flex 或 grid 容器里容易错位
- iOS Safari 对
swap的替换时机有延迟,有时卡在渲染队列末尾,肉眼可见闪动 - 如果字体文件本身没压缩、没做子集,
swap只是把“等 3 秒白屏”换成“等 3 秒乱排”,体验更差
WOFF2 + 子集化是体积控制的硬门槛
一个未处理的中文 .woff2 文件很容易超 2MB;英文项目也常因加载了整套 Unicode 符号而多出几百 KB。体积大 ≠ 服务器慢,而是传了根本用不到的字形:
- 中文必须子集:用
pyftsubset(fonttools)或transfonter按 HTML 中真实出现的字符生成精简版,3500 常用汉字子集通常能压到 200–400KB - 英文优先用
unicode-range拆分,比如只加载 ASCII + 常用标点:unicode-range: U+0020-007E, U+00A0-00FF; - Webpack/Vite 用户注意:
url-loader或asset/inline会把字体转成 Base64 写进 CSS,增大 CSS 体积且破坏缓存,应禁用
preload 关键字体必须带 crossorigin
<link rel="preload" as="font"> 能让浏览器在解析 HTML 阶段就发起字体请求,但它对配置极其敏感:
立即学习“前端免费学习笔记(深入)”;
- 必须加
crossorigin属性,哪怕字体和 HTML 同域,否则字体加载会失败(浏览器按匿名模式请求,而字体资源需 CORS 头) - 只预加载首屏关键字体,比如标题用的 Bold,正文 Regular 可延后;别对所有
@font-face都preload - 路径要绝对,MIME 类型写准:
type="font/woff2",否则部分浏览器忽略预加载 - 示例:
<link rel="preload" href="/fonts/pfsc-bold-subset.woff2" as="font" type="font/woff2" crossorigin>
移动端慎用 swap,optional + preload + 尺寸约束更稳
在弱网或低端 Android/iOS 设备上,swap 容易导致明显 FOUT 和布局抖动。真正可控的做法是组合使用:
- 用
font-display: optional:告诉浏览器“100ms 内加载不完就永远不用”,彻底规避等待与替换 - 但前提是字体 ≤200KB 且已
preload,否则永远 fallback - 同时给文字容器显式设置
font-size、line-height、height或min-height,锁住高度,防止替换前后尺寸变化引发重排 - 不要把多个
src格式(woff2 + woff + ttf)写在同一@font-face里,移动端解析开销高;单写url(...woff2)最稳妥
最常被忽略的一点:字体优化不是“一次配好就完事”,而是构建流程的一部分——子集提取要在转 WOFF2 之前做,preload 要和首屏 HTML 一起输出,font-display 要按字体重要性分级设置。漏掉任一环,其他优化都打折扣。


















