动态添加的为什么没触发预加载?

IT人柯佳 阅读 59

我在单页应用里想用JavaScript动态预加载字体文件,但发现资源始终没有被浏览器预加载。检查过网络面板,确实没有请求发出,代码逻辑看起来没问题,这是怎么回事?

我尝试过这样写:

document.addEventListener('DOMContentLoaded', () => {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.href = '/fonts/myfont.woff2';
  document.head.appendChild(link);
});

但字体加载时还是出现了FOIT闪烁。难道动态添加的preload需要额外设置什么属性吗?手动在HTML里写静态的就能生效,动态添加就失效了?

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
Tr° 一哲
你这个问题我也踩过坑,直接说原因:动态创建的 preload 标签虽然 DOM 上挂载了,但浏览器的预加载机制在文档解析阶段最敏感,DOMContentLoaded 触发时其实已经错过了最佳时机,这时候再 append link 元素,很多浏览器(尤其是 Chrome)根本不会去触发预加载行为。

解决办法是别等 DOMContentLoaded,越早越好,最好在脚本一执行就插入。你可以直接把这段逻辑放在一个内联 script 里,不要加任何事件监听:

const link = document.createElement('link');
link.rel = 'preload';
link.as = 'font';
link.href = '/fonts/myfont.woff2';
link.crossOrigin = 'anonymous'; // 如果字体跨域必须加这个,否则可能403或无效
document.head.appendChild(link);


注意关键点:
- 必须加 link.as = 'font',告诉浏览器这是字体资源
- 跨域字体一定要设 crossOrigin,不然可能加载失败但不报错
- 最好放在 head 里的内联脚本执行,不要等任何事件

如果你实在需要用动态时机控制,至少用 document.head.insertBefore(link, document.head.firstChild) 插到 head 最前面,提高被识别的概率。

我试过这方法在 Vue、React 的 SPA 里都能让 woff2 提前进预加载队列,FOIT 明显减少。希望能帮到你。
点赞 9
2026-02-11 19:34
若惜🍀
动态添加的 preload 确实有点坑,前端这块不少人都遇到过类似问题。主要原因是浏览器对动态插入的资源加载有优化策略,可能不会立刻执行预加载。

你这段代码逻辑本身没啥问题,但少了关键的 as 属性。对于字体文件,浏览器需要明确知道你要预加载的是什么类型的资源。所以你需要加上 as="font"crossorigin="anonymous",像这样:

document.addEventListener('DOMContentLoaded', () => {
const link = document.createElement('link');
link.rel = 'preload';
link.href = '/fonts/myfont.woff2';
link.as = 'font'; // 必须指定类型
link.crossOrigin = 'anonymous'; // 字体跨域必备
document.head.appendChild(link);
});


另外,FOIT(Flash of Invisible Text)的问题通常和字体加载策略有关。你可以考虑用 CSS 的 font-display 属性来优化显示行为,比如设置为 swapoptional

@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap; /* 推荐这种方式 */
}


最后提醒一下,动态 preload 虽然方便,但有些浏览器可能会稍微延迟处理这些请求,建议能静态写在 HTML 里的尽量静态写。如果必须动态加载,确保所有属性都配齐了,尤其是 ascrossorigin
点赞 12
2026-01-29 16:02