在前端开发中,「文件下载」几乎是每个项目都会遇到的功能。
有的需要导出 Excel / PDF 报表,有的要生成海报图,还有的需要带 Token 请求后端文件流。
本文系统整理 6 种前端文件下载方式,带你一次性理清它们的区别、原理和适用场景。
一、前端下载方式总览
| 方式 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
① <a download> | 原生属性触发浏览器下载 | 静态文件、本地资源 | ✅ 简单;❌ 不支持跨域、自定义名有限 |
② window.open() | 打开链接交给浏览器处理 | 后端已设置下载头 | ✅ 简单;❌ 无法监听结果 |
③ canvas + a 标签 | Canvas绘制图片转Base64 | 图片生成/截图 | ✅ 可处理图片;❌ 不能跨域 |
④ fetch + blob | 文件流转Blob创建URL | 任意类型文件下载 | ✅ 通用、安全;❌ 需现代浏览器 |
⑤ 后端 Content-Disposition | 服务端控制下载行为 | 大文件、导出报表 | ✅ 稳定安全;❌ 需后端支持 |
⑥ 第三方库 FileSaver.js | 封装Blob保存逻辑 | 跨浏览器下载文件 | ✅ 简化开发;❌ 需依赖库 |
二、各方式详解
1️、<a download> 直接下载
最简单的方式,无需 JS。
<a href="/files/demo.pdf" download="myFile.pdf">点击下载</a>
优点:
- 简洁、零代码。
- 可指定文件名(部分浏览器支持)。
缺点:
- 不支持跨域资源。
- 无法添加 Header 或 Token。
- 仅适用于静态文件。
适用场景:
同源的静态资源、前端打包文件、简单下载页。
2️、window.open() 打开下载链接
window.open('https://example.com/report.pdf')
优点:
- 最直接的方式,浏览器自动处理下载。
- 不用创建标签、兼容性好。
缺点:
- 无法控制文件名;
- 有可能被浏览器拦截;
- 无法判断下载是否成功。
适用场景:
后端接口已设置
Content-Disposition头部。
3️、canvas + a 标签(图片下载方案)
function DownLoad(href, name = 'image') {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = function () {
const canvas = document.createElement('canvas')
canvas.width = this.width
canvas.height = this.height
canvas.getContext('2d').drawImage(this, 0, 0)
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = `${name}.png`
a.click()
}
img.src = href + '?time=' + new Date().getTime()
}
优点:
- 可用于截图、生成分享图、导出图表。
- 不依赖后端。
缺点:
- 仅支持图片类型;
- 跨域图片会污染 Canvas,导致下载失败;
- 画质可能略有损失。
适用场景:
图片另存为、前端生成海报、截图导出。
4️、fetch + blob(通用文件流下载)
const handleDownload = async (url, filename) => {
const response = await fetch(url, { mode: 'cors' })
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = blobUrl
a.download = filename || url.split('/').pop()
a.click()
URL.revokeObjectURL(blobUrl)
}
优点:
- 支持任意文件类型(PDF、Excel、ZIP等)。
- 可携带 Token、Cookie;可鉴权。
- 下载的文件原样保存(无压缩)。
缺点:
- 需现代浏览器支持;
- 不支持断点续传。
适用场景:
绝大多数文件下载需求:接口返回文件流、带 Token 请求等。
5️、后端 Content-Disposition 控制下载
后端设置响应头:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="report.xlsx"
前端直接:
window.location.href = '/api/download/report?id=123'
优点:
- 浏览器原生下载;
- 不占用前端内存;
- 适合大文件(可断点续传)。
缺点:
- 需要后端支持;
- 无法自定义文件名(由后端控制)。
适用场景:
后端生成并导出大文件(如报表、日志包、导出 ZIP)。
6️、使用第三方库 FileSaver.js
import { saveAs } from 'file-saver'
fetch('/api/export')
.then(res => res.blob())
.then(blob => saveAs(blob, 'report.xlsx'))
优点:
- 封装完善,兼容 IE10+;
- 简化
fetch + blob操作。
缺点:
- 需额外依赖库;
- 原理与 Blob 相同。
适用场景:
大型项目、需要兼容旧浏览器时。
三、跨域下载问题说明
| 方案 | 跨域支持 | 说明 |
|---|---|---|
<a download> | No | 仅限同源文件 |
window.open() | Yes | 浏览器直接请求 |
canvas + a | No | 需服务端设置 Access-Control-Allow-Origin |
fetch + blob | Yes | 可通过 mode: 'cors' 处理 |
| 后端下载 | Yes | 由服务端控制 |
FileSaver.js | Yes | 基于 blob |
总结:如果你无法修改后端响应头,请优先选择
fetch + blob或后端导出方案。
四、选型建议
| 需求类型 | 推荐方案 |
|---|---|
| 下载同源静态文件 | <a download> |
| 导出图片或海报 | canvas + a 标签 |
| 接口返回文件流(带 Token) | fetch + blob |
| 大文件下载(报表、压缩包) | 后端 Content-Disposition |
| 需要兼容性和易用性 | FileSaver.js |
五、总结
“下载的本质,就是让浏览器获得一个可访问的文件流。”
- 图片场景 →
canvas + a - 普通文件 →
fetch + blob - 大文件/服务端导出 → 后端响应头
- 快速方案 →
<a download>/FileSaver.js
写在最后
开发中选方案,关键在于理解下载的“发起方”和“控制方”是谁。 前端能直接拿到数据的,就自己触发; 后端生成的文件,就交给服务器响应。