link.onerror在SPA中必须配合crossorigin="anonymous"和服务端CORS头使用,动态插入时需先绑定onerror再append,且Safari不支持onload,须用样式检测兜底。

link.onerror 在 SPA 中能直接用,但必须加 crossorigin
不加 crossorigin 时,跨域 CSS 加载失败只会触发空的 onerror 事件,event.target.href 拿不到、错误上下文全被浏览器屏蔽——这不是 bug,是同源策略对样式表的默认限制。SPA 里 CSS 多来自 CDN 或微前端子应用,基本都跨域,所以这步不能跳过。
正确写法是:<link rel="stylesheet" href="https://cdn.example.com/main.css" crossorigin="anonymous">,同时服务端必须返回匹配的 Access-Control-Allow-Origin 响应头(不能是 *,如果带凭据则需 Access-Control-Allow-Credentials: true)。
常见坑:
-
crossorigin="use-credentials"配错服务端头 → 请求直接失败,控制台报 CORS 预检或凭据错误,onerror可能都不触发 - 本地
file://协议下crossorigin完全无效,调试时要用本地 server - 只加
crossorigin但服务端没配 CORS 响应头 → 样式表静默丢弃,页面白屏,onerror参数仍为空
动态插入 link 时,onerror 必须在 append 前绑定
SPA 经常通过 JS 动态加载 CSS(比如路由切换时按需加载),这时 onerror 的赋值时机极关键:必须在 document.head.appendChild(link) 之前完成绑定,否则 Chrome/Firefox 在缓存命中或快速加载场景下会跳过事件触发。
立即学习“前端免费学习笔记(深入)”;
错误示例:document.head.appendChild(link); link.onerror = handler; —— 这种写法在生产环境大概率漏掉失败信号。
正确顺序只有这一种:
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/theme-dark.css';
link.crossOrigin = 'anonymous'; // 注意:JS 创建时用 crossOrigin 属性,不是 crossorigin
link.onerror = () => {
console.warn('CSS load failed, fallback to default');
link.href = '/theme-default.css';
};
document.head.appendChild(link);
注意点:
- JS 创建元素时属性名是
crossOrigin(驼峰),不是 HTML 中的crossorigin - 不要用
setTimeout延迟绑定,无法修复竞态问题 - 如果 fallback CSS 也跨域,同样要配
crossOrigin和服务端 CORS 头
仅靠 onerror 不足以判断“加载中”或“加载失败”
onerror 是个“单向失败信号”,但它有严重盲区:网络中断(请求根本没发出去)、超时、服务端返回 200 但内容为空或乱码——这些情况它都不会触发。这意味着你不能靠它来轮询“是否还在加载”,也不能把它当唯一失败依据。
更现实的做法是组合判断:
-
link.onload触发 → 成功(Chrome/Firefox/Edge 可靠,Safari 永远不触发) -
link.onerror触发 → 立即降级(前提是已配好crossOrigin) - 两者都没发生 → 启动轮询
getComputedStyle(testEl).fontFamily是否等于预设值(如"css-loaded-9b3"),用requestAnimationFrame控制节奏,5 秒超时后强制 fallback
测试元素 testEl 必须已挂载到 document.body,且样式规则里要写死一个稳定、不易被继承覆盖的属性,比如 font-family: "css-loaded-9b3",别用 color 或 display。
Safari 下 onload 完全不可用,别试图兼容它
Safari(包括 iOS 17 / macOS 14 及后续所有版本,截至 2026 年 9 月仍未变化)压根不派发 link.onload 事件。这不是临时缺陷,而是 Apple 主动选择不实现。任何基于 “onload + onerror” 二元判断的方案,在 Safari 下都会卡死或误判。
所以生产环境的 CSS 加载逻辑必须默认放弃 onload,只把 onerror 当作失败入口,再叠加样式检测兜底。最容易被忽略的一点是:很多人以为 Safari 的问题会随版本更新修复,但实际上这是长期策略,短期内不会变。


















