能绕过重复下载、避免FOIT/FOUT,关键在何时取、怎么用、如何更新:构建时子集化字体(50–200KB),requestIdleCallback异步存取ArrayBuffer,哈希驱动版本更新,JS动态注入FontFace实现精确调度。

直接用 IndexedDB 缓存字体文件,能绕过重复下载、避免 FOIT(Flash of Invisible Text)和 FOUT(Flash of Unstyled Text),让文字一上来就按设计字体渲染——关键不在“存”,而在“何时取、怎么用、如何更新”。
缓存前先做字体子集化
全量字体文件动辄几 MB,哪怕缓存了也拖慢首次解析。优先用 fontmin、font-spider 或 font-collector 提取页面实际用到的字符(比如只含中文标题+数字+常用标点),生成 50–200 KB 的子集字体。子集越小,IndexedDB 存取越快,渲染阻塞越短。
- 建议在构建阶段完成子集化,而非运行时动态提取(后者增加首屏延迟)
- 对多语言站点,可按 locale 拆分子集(如 zh-CN.woff2、en-US.woff2),避免单个大包
- 保留 fallback 字体声明(如
font-family: "Custom", system-ui;),确保子集缺失字符仍可读
用 requestIdleCallback + IndexedDB 异步加载
字体加载不该抢主线程资源。把下载和写库操作放在浏览器空闲时段执行:
- 检测页面可见性后,调用
requestIdleCallback启动字体获取流程 - 用
fetch获取字体二进制(response.arrayBuffer()),不转 base64,避免额外编码开销 - 写入 IndexedDB 时,使用
put()存 ArrayBuffer,不是字符串——原生支持二进制,读取时可直接传给FontFace构造函数 - 写入成功后,立即调用
fontFace.load()触发字体就绪,但不阻塞渲染
加载时优先查库,失败再回退网络
字体使用前,必须同步判断是否已缓存并可用:
- 从 IndexedDB 读取对应字体 ArrayBuffer,创建
FontFace实例:new FontFace(family, arrayBuffer) - 调用
load()并 awaitfontFace.loaded,成功则document.fonts.add(fontFace) - 若 DB 未命中或加载失败,降级发起 fetch 请求,并在成功后补存到 DB —— 实现“用即存”闭环
- 避免在 CSS
@font-face中硬编码 URL,全部由 JS 动态注入,才能控制加载时机
版本控制靠内容哈希,不靠时间戳
设计师改了一个字形,字体文件就该更新;但仅靠文件名或修改时间极易误判。稳妥做法是:
- 构建时对字体文件计算 MD5 或 SHA-256,作为缓存 key(如
custom-zh-v2.3.1-8a7f9c...) - 运行时比对当前请求的哈希值与 DB 中存储的 key,不一致则清除旧记录、写入新数据
- 可将哈希值嵌入 JS bundle 或通过接口下发,避免每次都要 fetch 头部校验
- 不建议用 DB version 升级来清理字体——version 变更会清空整个库,风险过高
这套流程不依赖第三方库,核心就是三件事:子集瘦身、异步存取、哈希驱动更新。真正难的不是代码,而是把字体加载从“CSS 自动触发”切换成“JS 精确调度”。

















