根本原因是浏览器仅允许同源url使用download属性触发下载;跨域链接或file://协议下该属性失效,需用fetch+blob+createobjecturl中转,或服务端配置content-disposition响应头。

为什么 <a></a> 标签加 download 属性有时不生效
根本原因就一个:浏览器只允许同源 URL 使用 download 属性触发下载。如果你的 href 是跨域链接(比如指向 https://cdn.example.com/file.pdf),点击后会直接跳转或打开,不会下载——哪怕写了 download="xxx" 也没用。
常见错误现象:download 属性存在但点击后文件在新标签页打开、或直接 404、或控制台报 CORS 错误;还有一种是本地双击 html 文件(file:// 协议),此时绝大多数浏览器完全忽略 download。
- 必须确保
href指向同源资源(如/assets/report.xlsx或./data.json) - 服务端需正确返回
Content-Disposition: attachment头(非必需但更可靠) - 避免在
file://环境下测试,用本地服务器(如python3 -m http.server)
怎么让非同源链接也能“假装”下载
当你要下载 CDN 上的图片、远程 API 返回的 Blob 数据,或者后端没配好头信息时,不能靠纯 HTML 解决,得用 JS 中转。
核心思路:用 fetch 拿到二进制数据 → 转成 Blob → 创建 URL.createObjectURL() → 动态创建 <a></a> 并触发点击。
立即学习“前端免费学习笔记(深入)”;
- 注意:
fetch请求需服务端允许 CORS,否则拿不到响应体 -
Blob类型要尽量匹配原始文件(如application/pdf),否则可能下载后打不开 - 调用
URL.revokeObjectURL()清理内存(尤其在循环下载多个文件时)
const link = document.createElement('a'); link.href = '/api/export?format=csv'; link.download = 'report.csv'; <p>fetch(link.href) .then(r => r.blob()) .then(blob => { link.href = URL.createObjectURL(blob); link.click(); URL.revokeObjectURL(link.href); });
download 属性对文件名的影响和限制
它只起“建议作用”,不是强制重命名。浏览器最终保存的文件名取决于三者优先级:Content-Disposition 响应头 > download 属性值 > URL 路径最后一段。
比如 <a href="/files/123" download="invoice.pdf"></a>,如果服务端返回 Content-Disposition: attachment; filename="bill-2024.pdf",那用户看到的就是 bill-2024.pdf,不是 invoice.pdf。
- windows 用户可能遇到文件名含非法字符(如
/ : * ? " |)被截断,浏览器会静默处理 - 移动端 safari 对
download支持极差,基本无效,必须走服务端直出或Content-Disposition - 不要依赖
download来隐藏真实路径——它只是提示,URL 依然可见且可被右键另存为
服务端配合才是最稳的下载方式
前端所有技巧都是补救。真正可靠的下载,靠服务端设置两个关键响应头:
-
Content-Disposition: attachment; filename="xxx.pdf"(明确告诉浏览器这是附件,且指定默认名) -
Content-Type: application/octet-stream(或具体类型,如application/pdf,避免被浏览器尝试渲染)
这时候连 <a href="/export"></a> 都不用加 download,点开就下载。nginx、express、django 都能轻松配置,比前端各种兼容写法省心得多。
容易被忽略的一点:如果服务端返回了 302 重定向,而重定向目标没有设置 Content-Disposition,那下载还是会失败——浏览器只看最终响应头,不继承重定向前的头信息。