font-display: swap 必须显式写在 @font-face 规则内,否则浏览器按 auto(等同 block)处理,导致文字空白约 100ms 后最多等待 3 秒;需配合 preload(含 crossorigin、as="font"、type="font/woff2")、format("woff2") 声明及度量匹配的 fallback 字体。

font-display: swap 必须写在 @font-face 里
不写 font-display: swap,浏览器就按 auto(实际等同 block)处理:文字先空白约 100ms,再最多等 3 秒加载字体,失败才 fallback。这不是“没生效”,是浏览器在守默认规则。
常见失效场景包括:
- @font-face 中漏了
format("woff2"),旧版 Safari/Edge 会直接跳过整条规则,font-display自然不生效 - 用
@import引入字体 CSS,导致解析延迟,font-display生效时机滞后 - 字体路径是相对路径(如
url(./fonts/Inter.woff2)),在file://协议下 404,整个@font-face被丢弃
preload 字体必须带 crossorigin 和 as="font"
<link rel="preload"> 是唯一能在 HTML 解析阶段就触发字体下载的手段,但错一个属性就静默失效——Network 面板里根本看不到请求,也不报错。
必须同时满足:
立即学习“前端免费学习笔记(深入)”;
-
as="font"(不能写成"fetch"或留空) -
crossorigin属性(值可为空:crossorigin=""或crossorigin) -
type="font/woff2"(匹配实际 MIME 类型) -
href路径与@font-face中的src完全一致(协议、域名、路径、查询参数都不能差)
漏掉 crossorigin,哪怕字体跟页面同源,CORS 策略也会直接丢弃响应;字体服务没配 Access-Control-Allow-Origin: *,控制台就会报 CORS error。
fallback 字体链要分平台且度量对齐
写了 font-display: swap 还看到文字“跳一下”,大概率不是字体没加载完,而是 fallback 字体(比如系统 PingFang SC)和自定义字体在 line-height、ascent、descender 上不一致,替换时触发重排。
解决方案不是换字体,而是选对 fallback 并微调:
- 中文字体 fallback 按平台分层:
"PingFang SC"(macOS/iOS)、"Microsoft YaHei"(Windows)、"Noto Sans CJK SC"(Linux) - 英文 fallback 紧跟其后,统一加引号:
"Inter", "PingFang SC", "Microsoft YaHei", sans-serif - 若图标字体或窄体字出现下沉,需在
@font-face中加size-adjust或ascent-override,数值来自opentype.js解析 OS/2 表 - 避免用
font-size: 1.2em强制缩放图标字体——这会让 fallback 和 target 的基线彻底错位
localStorage 记录子集哈希实现二次加载加速
首次加载后存下字体子集标识,后续访问跳过重复请求,配合 font-display: swap 实现真正平滑过渡。但 localStorage 不是“开关”,是状态协调器。
关键点:
- 只存轻量元数据:
font-subset-hash-zh(子集哈希)、user-lang(语言标识)、font-loaded-title(渲染标记) - 读取时必须用
localStorage.getItem('x') !== null判断存在性,而非if (localStorage.getItem('x'))——字符串"false"是真值 - 写入前要
JSON.stringify(),读取后用try/catch解析,防解析失败阻断流程 - 无痕模式或 Safari 私密浏览下,
localStorage可能静默失败,需降级到基础字体或捕获QuotaExceededError
最易被忽略的是:document.fonts.load() 必须在检查 localStorage 状态后主动调用,否则缓存了哈希也未必触发字体就绪事件;而 document.fonts.ready 是最终确认点,不是替代方案。



















