浏览器静默忽略跨域a标签download属性,需用fetch+Blob+URL.createObjectURL()实现下载;注意CORS配置、blob处理、及时revokeObjectURL及服务端Content-Disposition配合。

直接结论:浏览器会静默忽略跨域 a 标签的 download 属性,这不是 bug,而是同源策略的强制行为。必须改用 fetch + Blob + URL.createObjectURL() 的组合绕过限制。
为什么 download 在跨域时完全不生效
Chrome、Firefox、Safari 都遵循同一规则:只有 href 指向同源 URL(或 blob:、data: 协议)时,download 才会被执行。一旦是跨域地址(哪怕只是协议不同 http vs https,或端口不同 :8080 vs :3000),浏览器就退化为普通跳转——你点下去,文件可能在新标签页打开(如 PDF),也可能直接预览(如 PNG),但绝不会下载,且控制台不报错。
常见误判场景包括:
- 本地双击 HTML 文件(
file://协议),哪怕资源和 HTML 在同一文件夹,也被视为跨域 - CDN 域名(
cdn.example.com)与主站(app.example.com)二级域名不同 - 后端返回了
Content-Disposition: attachment,但这和download属性无关,两者机制独立
fetch + Blob 下载必须注意的三件事
这是目前最通用、兼容性最好的方案,但容易在细节上翻车:
立即学习“前端免费学习笔记(深入)”;
-
fetch必须显式传{ mode: 'cors' },否则请求可能被拒绝(即使服务端已配Access-Control-Allow-Origin) - 响应体必须用
response.blob(),不是response.text()—— 后者对二进制文件(Excel、PDF、图片)会损坏数据 - 每次下载都要调用
URL.createObjectURL(blob)创建新 URL,并在点击后立即执行URL.revokeObjectURL(url),否则内存泄漏,尤其用户频繁下载时
示例关键逻辑:
const blob = await res.blob(); const blobUrl = URL.createObjectURL(blob); a.href = blobUrl; a.download = filename; a.click(); URL.revokeObjectURL(blobUrl); // 这行不能漏
服务端配合要点:CORS 和文件名提取
前端方案依赖服务端正确响应:
- 响应头必须包含
Access-Control-Allow-Origin,值不能是*如果前端fetch带了credentials: 'include' - 推荐服务端通过
Content-Disposition返回建议文件名,前端可用正则提取:res.headers.get('content-disposition')?.match(/filename[^;=\n]*=([^;]*)/)?.[1] - 如果服务端没返回
Content-Disposition,且前端无法预知文件扩展名,要自己根据 URL 或 MIME 类型补全,比如.pdf、.xlsx,否则下载后可能无后缀、打不开
容易被忽略的边界情况
真正上线时,这几个点常被跳过:
- 大文件(>100MB)用
fetch可能卡 UI 或触发内存警告,需加 loading 提示或分块处理(但download本身不支持断点) - Safari 对
blob:URL 的click()支持不稳定,有时需加setTimeout(() => a.click(), 0)强制异步 - 某些内嵌 WebView(如微信、钉钉)可能禁用
URL.createObjectURL,得降级为服务端直链 +target="_blank",靠后端设Content-Disposition
跨域下载本质是“把远程资源拉到本地再导出”,所有逻辑都围绕这个前提展开。没捷径,也不能省掉 revokeObjectURL 或假设服务端一定返回文件名。



















