非关键 CSS 必须异步引入,推荐 rel="preload"+onload 切换 rel 方案;动态插入易引发预加载绕过、布局抖动、SSR 不一致等问题;loadCSS 库更可靠,支持兼容性 fallback 和精确回调;异步 CSS 需人工控制加载顺序与级联依赖。

非关键 CSS 必须异步引入,否则它会阻塞页面渲染,哪怕只多 200KB,首屏时间也可能延迟 800ms 以上。关键不是“能不能异步”,而是“怎么异步才不翻车”。
用 rel="preload" + onload 切换 rel 是最稳的方案
这是目前兼容性最好、行为最可控的方式,连 IE11 都能跑,且不会触发 FOUC 或布局抖动。
-
rel="preload"让浏览器立即发起请求,但不解析、不应用样式 -
onload回调里把rel改成"stylesheet",此时才真正注入 DOM 并生效 - 必须加
this.onload = null,否则在某些 Android WebView 中会重复触发 - 不能省略
as="style",否则 Chrome 会报 MIME 类型警告,还可能降级为普通fetch
示例写法:
<link rel="preload" href="modal.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
动态插入 <link> 标签容易踩哪些坑
看似简单,但 JS 插入 <link> 在真实项目中常出问题:
立即学习“前端免费学习笔记(深入)”;
- 绕过浏览器预加载器:插入时机晚,资源下载滞后,首屏后才开始拉 CSS
- 强制同步布局:如果在
document.write或 DOM ready 后立刻插入并读取offsetHeight,会触发 layout thrashing - SSR 不一致:服务端没渲染该 link,客户端插入后样式闪一下(尤其 Next.js / Nuxt 等框架)
- 没有加载状态反馈:你不知道它到底有没有加载成功,
onerror监听又不可靠(比如 CORS 或 404 不一定触发)
真要用 JS 插入,建议包裹在 requestIdleCallback 里,或至少用 setTimeout(..., 0) 推迟到下一个宏任务。
为什么 loadCSS 库比手写更可靠
loadCSS 不是“多此一举”,它封装了几个关键细节:
- 用
media="only x"初始化 link,避免阻塞渲染(比rel="preload"兼容旧版 Safari 更稳) - 自动 fallback 到
onload/onreadystatechange/ 定时轮询,覆盖 IE8–Android 4.3 等边缘情况 -
onloadCSS()提供精确的加载完成回调,比监听link.onload更准(后者在某些缓存场景下不触发) - 返回的
<link>元素可直接操作,比如加data-loaded属性或绑定主题切换逻辑
最小可用示例:
<script src="dist/loadCSS.js"></script>
<script>
const themeLink = loadCSS("theme-dark.css");
onloadCSS(themeLink, () => {
document.documentElement.setAttribute("data-theme", "dark");
});
</script>
异步 CSS 的加载顺序和级联风险必须人工控制
异步 ≠ 无序。如果你的 form-controls.css 依赖 base.css 里的 .btn 基础类,而两者都异步,就可能因为加载时序错乱导致样式丢失。
- 不能靠
Promise.all控制顺序——CSS 加载不返回 Promise - 必须让 base.css 用内联
<style>或同步<link>保证最早执行 - 非关键 CSS 之间如果有依赖,得用
onloadCSS链式调用,例如先等 A 加载完再loadCSS(B) - 构建阶段就要做静态依赖分析(如 PostCSS + css-tree),生成加载拓扑,不能靠运行时猜
最容易被忽略的一点:Safari 对异步 CSS 的 GPU 合成有特殊要求,如果 modal.css 里用了 transform 动画,但未提前触发合成层,动画仍会卡顿——这时得在 onloadCSS 回调里加一句 document.body.style.willChange = "transform" 手动提示。



















