font-display: swap 是第三方字体按需加载最有效、最低成本的起点:它不改HTML、不写JS,强制系统字体立即渲染并等woff2就绪后无感替换;必须写在@font-face内,跨域时需加crossorigin,旧版Safari兼容性受限。

font-display: swap 是第三方字体按需加载最有效、最低成本的起点——它不改 HTML 结构,不写一行 JS,就能避免 FOIT(空白闪动)和 FOUT(样式闪动)的极端表现。
为什么直接写 @font-face 还是卡顿?
常见错误不是没写 @font-face,而是漏掉 font-display 或设成 block。浏览器默认行为(auto)在 Chrome/Firefox 中会阻塞文本渲染约 3 秒,期间文字不可见;设为 block 更糟,它延长隐形等待期。而 swap 让系统字体立刻上屏,等 .woff2 下载完再无感替换。
必须注意三点:
-
font-display只在@font-face规则内生效,不能写在body或类选择器里 - 若字体文件跨域(比如放在 CDN),
crossorigin属性必须加在<link>或@font-face的src中,否则字体加载失败且无报错 - 旧版 Safari(swap,但会优雅降级为
auto,不影响功能,仅略多等 1–2 秒
<link rel="preload"> 预加载只对首屏关键字体有效
预加载不是“把所有字体都提前下”,而是精准狙击首屏立即可见的文字,比如 <h1> 标题或 Logo 文字。如果页面用 Inter-Bold 渲染标题、Inter-Regular 渲染正文,那就只 preload Bold 版本。
立即学习“前端免费学习笔记(深入)”;
实操要点:
- 必须带
as="font"和type="font/woff2",否则浏览器不会复用该资源,等于白写 -
crossorigin属性不能省——哪怕字体和 HTML 同源,Chrome 仍要求显式声明 - 不要和
font-display: optional共存:optional表示“加载慢就放弃”,和preload的意图完全冲突
示例:
<link rel="preload" href="/fonts/inter-bold-latin.woff2" as="font" type="font/woff2" crossorigin>
按需加载 ≠ 运行时判断,而是构建时切分
所谓“按需”,95% 场景下指构建阶段就拆:不同字重分文件、中英文子集分离、变量字体(variable font)慎用。一个 inter-var-latin.woff2 300KB,但首页可能只用 Regular + Bold + ASCII 字符,压到 60KB 内很现实。
推荐做法:
- 用
pyftsubset(fonttools)生成子集,命令里明确指定--text="A-Za-z0-9 ",避免把整套 Unicode 拉进来 - 把
font-weight: 100 900的 variable font 拆成inter-regular.woff2、inter-bold.woff2等独立文件,CSS 中按需@import或引用 - 别在 CSS 中堆叠多个
src备用格式(如.woff2, .woff, .ttf)——现代浏览器只认.woff2,多余格式徒增解析负担
结构展位(skeleton / placeholder)与字体加载要解耦
很多人用骨架屏(skeleton)掩盖字体加载延迟,但这治标不治本:骨架屏本身依赖字体渲染来对齐高度,如果 font-display: swap 还没生效,骨架元素的行高、letter-spacing 仍不稳定,导致“骨架跳动”。
真正可控的做法是:
- 骨架结构用系统字体(如
system-ui)定义尺寸,确保宽高固定,不随自定义字体加载而变 - 文字内容区域用
font-family: "YourFont", system-ui,靠swap保证视觉平滑过渡 - 避免给骨架元素设
font-weight或font-style,这些属性在字体未就绪时会触发额外重排
复杂点在于:字体加载完成事件(document.fonts.load)不可靠——它不区分字重,也不反馈子集是否完整加载。所以展位逻辑不该等“字体加载完”,而应信任 swap 的行为,让 DOM 渲染节奏由 CSS 控制,而非 JS。



















