本文总结了JavaScript中实现单文件下载的几种常见方案,包括使用a标签download属性、通过Blob处理非同源资源、利用iframe触发下载以及后端代理模式。重点解析了浏览器触发自动下载的关键响应头Content-Disposition和Content-Type,帮助开发者解决同源与非同源文件的下载难题。
近期在开发文件下载功能时,对所用技术及遇到的常见问题进行了简要梳理与总结。
a标签与Blob下载方案
1. 单文件下载(a标签方式)
同源单文件场景
对于同源的单个文件,可以直接利用HTML中<a>标签的download属性来实现文件下载。
const elt = document.createElement('a');
elt.setAttribute('href', url);
elt.setAttribute('download', 'file.png');
elt.style.display = 'none';
document.body.appendChild(elt);
elt.click();
document.body.removeChild(elt);
然而,该方案并不适用于非同源资源。在这种情况下,点击链接相当于普通超链接跳转,会直接打开资源页面而非触发下载。
非同源图片处理
若不存在跨域资源共享(CORS)限制,可通过构造XMLHttpRequest请求文件地址,并以Blob形式接收响应来实现下载。
function downloadWithBlob(url) {
fetch(url).then(res => res.blob().then(blob => {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
var filename = 'file.png';
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}));
}
若存在CORS跨域问题,可考虑使用Canvas将图片转换为base64编码,随后利用<a>标签的download属性进行下载。
function downloadPic(url) {
const img = new Image;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = function() {
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(this, 0, 0);
const elt = document.createElement('a');
elt.setAttribute('href', canvas.toDataURL('image/png'));
elt.setAttribute('download', 'file.png');
elt.style.display = 'none';
document.body.appendChild(elt);
elt.click();
document.body.removeChild(elt);
};
img.crossOrigin = 'anonymous';
img.src = url;
}
iframe下载与触发机制
2. 单文件下载(iframe方式)
iframe方案的核心思路是在页面中隐藏一个iframe,将下载地址加载至该iframe中,从而触发浏览器的默认下载行为。
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
但实践中发现,即使是同域的图片也无法通过此方式完成下载,其原因何在?
这里涉及一个a标签下载中未提及的关键问题:究竟什么样的链接能触发浏览器的自动下载行为?
URL触发浏览器自动下载的机制
一个URL能否触发浏览器自动下载,主要取决于该请求的响应头(response header)是否满足特定条件,通常需检查Content-Disposition和Content-Type这两个消息头。
- 当response header中指定Content-Disposition为attachment时,表示要求浏览器将消息体作为附件下载并保存至本地(通常还会指定filename,下载文件名默认即为该值)。
- 当response header中指定Content-Type为application/octet-stream(无类型)或application/zip(zip包)等时,浏览器也会触发下载。其中application/octet-stream表示HTTP响应为二进制流(未指定明确类型),常用于未知应用程序文件,浏览器一般不会自动执行或询问执行,而是像对待设置了HTTP头Content-Disposition值为attachment的文件一样处理此类文件。
只要URL满足上述触发要求,即可通过iframe形式实现下载。
后端代理服务处理
3. 代理服务处理下载
如果后端具备控制权或能配合前端,可以编写一个代理服务。后端负责请求文件数据,设置好相应的response header后,前端请求该代理服务以完成下载。
前端实现(假设代理服务接口为http://exampale.com/download):
const downloadUrl = 'http://exampale.com/download?url=' + encodeURIComponent(url) + '&name=xxx';
const elt = document.createElement('a');
elt.setAttribute('href', downloadUrl);
elt.setAttribute('download', 'file.png');
...
后端实现
const url = decodeURIComponent(req.query.url);
http.get(url, (response) => {
res.setHeader('Content-disposition', 'attachment;filename=' + req.query.name);
res.setHeader('Content-type', 'application/octet-stream');
response.pipe(res);
});
单文件处理的讨论暂告一段落,多文件下载将在后续文章中补充。
以上为本文全部内容,希望对各位的学习有所帮助,也欢迎大家继续支持本站。