js实现文件下载是前端开发中非常常见的需求,不同的业务场景对应不同的实现方案,开发者可以根据文件来源和类型选择合适的下载方式。
基于a标签的静态文件下载
如果要下载的是服务器上已经存在的静态文件,比如提前上传好的文档、图片等,最直接的方式是利用<a>标签的download属性实现下载。
这种方式的原理是给a标签设置href指向文件的访问地址,同时添加download属性指定下载后的文件名,当用户点击a标签时,浏览器就会触发文件下载而不是打开文件。
基础实现代码如下:
// 下载静态文件的函数
function downloadStaticFile(fileUrl, fileName) {
const link = document.createElement('a');
// 设置文件访问地址
link.href = fileUrl;
// 设置下载后的文件名
link.download = fileName || 'default_file';
// 将a标签添加到页面中
document.body.appendChild(link);
// 触发点击事件
link.click();
// 移除a标签
document.body.removeChild(link);
}
// 调用示例,下载服务器上的test.pdf文件
downloadStaticFile('https://ipipp.com/files/test.pdf', '测试文档.pdf');
需要注意,如果文件地址和当前页面不在同一个域名下,且没有配置CORS跨域,那么download属性可能会失效,浏览器会直接打开文件而不是触发下载。
基于Blob对象的接口文件流下载
很多时候文件不是静态存在的,而是需要通过后端接口动态获取,接口返回的是文件流数据,这时候就需要用Blob对象来处理文件流实现下载。
Blob对象可以存储大量的二进制数据,我们可以将接口返回的文件流转换为Blob对象,再生成临时的URL赋值给a标签的href属性,从而触发下载。
以fetch请求接口获取文件流为例,实现代码如下:
// 下载接口返回的文件流
async function downloadFileByStream(apiUrl, fileName, fileType) {
try {
// 发起请求获取文件流,需要设置responseType为blob
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
// 将响应数据转换为Blob对象
const blob = await response.blob();
// 生成Blob对象的临时访问URL
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName || 'downloaded_file';
document.body.appendChild(link);
link.click();
// 释放临时URL,避免内存泄漏
URL.revokeObjectURL(blobUrl);
document.body.removeChild(link);
} catch (error) {
console.error('文件下载失败:', error);
}
}
// 调用示例,请求下载接口获取excel文件
downloadFileByStream('https://ipipp.com/api/download/excel', '报表数据.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
如果接口需要携带token等认证信息,可以在fetch的headers中添加对应的认证字段,确保请求能够正常通过鉴权。
不同文件类型的处理注意事项
不同类型的文件在下载时可能需要设置对应的MIME类型,避免浏览器识别错误导致下载异常。常见的文件类型对应的MIME类型如下:
| 文件类型 | MIME类型 |
|---|---|
| PDF文档 | application/pdf |
| Excel表格(xlsx) | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| Word文档(docx) | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| PNG图片 | image/png |
| 文本文件 | text/plain |
如果不确定文件的MIME类型,可以省略类型设置,浏览器会自动根据文件内容推断类型,不过为了兼容性更好,建议尽量明确指定对应的MIME类型。
常见问题解决
跨域导致download属性失效
如果文件地址跨域且没有配置CORS,a标签的download属性会不生效,这时候可以通过后端接口转发文件流,再用Blob对象的方式下载,避免跨域问题。
下载文件名乱码
如果下载的中文文件名出现乱码,可以在后端设置响应头Content-Disposition时,对文件名进行URL编码,前端拿到文件名后再解码即可。如果后端已经正确设置,前端也可以手动处理文件名:
// 处理文件名乱码的函数
function decodeFileName(fileName) {
// 尝试用decodeURIComponent解码
try {
return decodeURIComponent(fileName);
} catch (e) {
return fileName;
}
}
大文件下载进度展示
如果需要展示大文件的下载进度,可以使用XMLHttpRequest代替fetch,监听progress事件获取下载进度:
function downloadWithProgress(apiUrl, fileName) {
const xhr = new XMLHttpRequest();
xhr.open('GET', apiUrl, true);
xhr.responseType = 'blob';
// 监听下载进度
xhr.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`下载进度:${percent}%`);
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(blobUrl);
document.body.removeChild(link);
}
};
xhr.send();
}