共计 6283 个字符,预计需要花费 16 分钟才能阅读完成。
提醒:本文最后更新于2025-07-07 14:50,文中所关联的信息可能已发生改变,请知悉!
在 html5 中,a
标签新增了 download
属性,包含该属性的链接被点击时,浏览器会以下载文件方式下载 href
属性上的链接。示例:
<a href="https://www.baidu.com" download="baidu.html">下载</a>
1. 前端 js 下载实现与示例
通过 javascript 动态创建一个包含 download
属性的 a
元素,再触发点击事件,即可实现前端下载。
代码示例:
function download(href, title) { | |
const a = document.createElement('a'); | |
a.setAttribute('href', href); | |
a.setAttribute('download', title || href.slice(href.lastIndexOf('/') + 1); | |
a.onclick = function (e) { e.stopPropagation(); } | |
a.click(); | |
} |
说明:
href
属性设置要下载的文件地址。这个地址支持多种方式的格式,因此可以实现丰富的下载方法。download
属性设置了下载文件的名称。但href
属性为普通链接并且跨域时,该属性值设置多数情况下会被浏览器忽略。
1.1 普通连接下载示例
// 下载图片 | |
download('https://lzw.me/images/gravatar.gif', 'lzwme-gravatar.gif'); | |
// 下载一个连接 | |
download('https://lzw.me', 'lzwme-index.html'); |
1.2 href 为 data URIs 示例
data URI 是前缀为 data:scheme
的 URL,允许内容创建者在文档中嵌入小文件。数据URI由四个部分组成:前缀(数据:),指示数据类型的MIME类型,如果非文本则为可选的base64令牌,数据本身:
data:[<mediatype>][;base64],<data>
链接的 href 属性为 data URIs 时,也可以实现文件内容的下载。示例:
download('data:,Hello%2C%20World!', 'data-uris.txt'); | |
download('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D', 'data-uris.txt'); |
1.3 canvas 下载示例
对于 canvas 可以通过 toDataURL
方法取得 data URIs 格式的内容。
const canvas = document.querySelector('#canvas'); | |
download(canvas.toDataURL(), 'download-image'); |
1.4 二进制内容下载
URL.createObjectURL
方法会根据传入的参数创建一个指向该参数对象的 URL。新的对象 URL 指向执行的 File 对象或者是 Blob 对象。
URL.createObjectURL
的参数是 File 对象或者 Blob 对象,File 对象也就是通过 input[type=file] 选择的文件,Blob 对象是二进制数据。
将URL.createObjectURL
返回值设为 href 属性的值,即可实现二进制内容下载。示例:
const content = 'Welcome to lzw.me!'; | |
const blob = new Blob([content]); | |
const href = URL.createObjectURL(blob); | |
download(href, 'download-text.txt'); | |
URL.revokeObjectURL(href); |
1.5 前端下载方法示例
综合上述讨论,这里给出一个前端实现下载的 saveAs
方法的 TypeScript
示例:
/** | |
* 通过创建 a dom 对象方式实现前端文件下载 | |
* @param href 要下载的内容链接。当定义了 toBlob 时,可以为纯文本或二进制数据(取决于 toBlob 格式 | |
* @param fileName 下载后的文件名称 | |
* @param toBlob 如设置该参数,则通过 blob 方式将 href 转换为要保存的文件内容,该参数将入参为 new Blob([href], toBlob) 的第二个参数 | |
* @example | |
* ```js | |
* saveAs('abc', 'abc.txt', {}); | |
* saveAs('data:,Hello%2C%20World!', 'hello.txt'); | |
* saveAs('https://lzw.me/images/avatar/lzwme-80x80.png', 'lzwme-logo.png'); | |
* ``` | |
*/ | |
export function saveAs(href: string | Blob, fileName?: string, toBlob?: PlainObject) { | |
const isBlob = href instanceof Blob || toBlob; | |
if (!fileName && typeof href === 'string' && href.startsWith('http')) { | |
fileName = href.slice(href.lastIndexOf('/') + 1); | |
} | |
fileName = decodeURIComponent(fileName || 'download'); | |
if (typeof href === 'string' && toBlob) href = new Blob([href], toBlob); | |
if (href instanceof Blob) href = URL.createObjectURL(href); | |
const aLink = document.createElement('a'); | |
aLink.setAttribute('href', href); | |
aLink.setAttribute('download', fileName); | |
aLink.onclick = function (e) { e.stopPropagation(); } | |
aLink.click(); | |
// const evt = document.createEvent("HTMLEvents"); | |
// evt.initEvent("click", false, false); | |
// aLink.dispatchEvent(evt); | |
if (isBlob) setTimeout(() => URL.revokeObjectURL(aLink.href), 100); | |
return aLink; | |
} |
2. 检测浏览器是否支持 download 属性
download 属性为 html5 新增内容,浏览器支持情况可参考:http://caniuse.com/#feat=download
判断浏览器是否支持该属性,只需要检测 a 标签是否存在 download 属性。示例:
const downloadAble = 'download' in document.createElement('a');
对于不支持的浏览器,只能另想他法或者予以降级处理了。
3. 使用 serviceWorker 和 fetch API 代理实现
前端下载更多的需求是因为内容产生于前端。那么可以在后端实现一个这样的 API ,它在接收到前端发出的内容后返回下载格式的数据。这种实现就不存在浏览器兼容问题。
利用 serviceWorker 和 fetch API 截拦浏览器请求,只需实现好约定逻辑,也可仅在纯前端实现这种功能需求。示例:
在页面中,通过 fetch API 构造请求:
fetch('lzwme.txt', { | |
isDownload: true, | |
body: { | |
data: new Blob('hi!'), | |
filename: 'lzwme.txt', | |
} | |
}) |
在 serviceWorker 中,截拦附带 isDownload 头信息的请求,构造下载回应:
self.addEventListener('fetch', function(event) { | |
const req = event.request; | |
if (!req.headers.get('isDownload')) { | |
retrun fetch(req); | |
} | |
const body = req.headers.get(body); | |
const filename = body.filename || encodeURIComponent(req.url); | |
const contentType = req.headers.get('Content-Type') || 'application/force-download'; | |
const disposition = "inline;filename=" + filename + ";filename*=utf-8''" + filename | |
event.respondWith( | |
new Response(body.data, { | |
headers: { | |
'Content-Type': contentType, | |
'Content-Disposition': disposition | |
} | |
}) | |
); | |
}); |
提示:以上代码仅作思路示例,未作实际应用测试。
4 使用 ajax (xhr与fetch API) 方式下载服务器文件
以上主要讨论的是纯前端实现下载保存文件的方法。对于下载服务器文件,最简的方式就是 window.open(url)
和 location.href=url
了,但是其的弊端也很明显,当出错时整个页面都会挂掉,而且也无法获得下载状态与进度,下载时间稍长时体验相当不好。
下面介绍一下使用 xhr 和 fetch API 实现文件下载的方法。其主要思路为:将请求结果设为 Blob 类型,然后采用前端下载保存 Blob 类型数据的方式实现下载。
4.1 使用 xhr 下载远程服务器文件
代码示例:
/** 前端下载/保存文件 */ | |
function saveAs(href, fileName) { | |
const isBlob = href instanceof Blob; | |
const aLink = document.createElement('a'); | |
aLink.href = isBlob ? window.URL.createObjectURL(href) : href; | |
aLink.download = fileName; | |
aLink.click(); | |
if (isBlob) setTimeout(() => URL.revokeObjectURL(aLink.href), 100); | |
} | |
function xhrDownload(url, options = {}) { | |
options = Object.assign({ method: 'get', headers: {} }, options); | |
return new Promise((reslove, reject) => { | |
const xhr = new XMLHttpRequest(); | |
xhr.responseType = 'blob'; // options.responseType; | |
if (options.headers) { | |
for (const key in options.headers) xhr.setRequestHeader(key, options.headers[key]); | |
} | |
xhr.onload = () => { | |
// 从 Content-Disposition 中获取文件名示例 | |
const cd = xhr.getResponseHeader('Content-Disposition'); | |
if (cd && cd.includes('fileName') && !options.fileName) options.fileName = cd.split('fileName=')[1]; | |
options.fileName = decodeURIComponent(options.fileName || 'download-file'); | |
if (+xhr.status == 200) { | |
saveAs(xhr.response, options.fileName); | |
reslove(options.fileName); | |
} | |
}; | |
xhr.onerror = err => reject(err); | |
xhr.addEventListener( | |
'progress', | |
event => { | |
if (options.onProgress) options.onProgress(event.loaded, event.total, event); | |
}, | |
false | |
); | |
xhr.open(options.method, url, true); | |
xhr.send(options.body); | |
}); | |
} | |
// 测试 | |
xhrDownload('https://lzw.me/images/avatar/lzwme-80x80.png') | |
.then(() => console.log('文件下载完成')) | |
.catch(err => console.log('文件下载失败', err)); |
4.1 使用 fecth API 下载远程服务器文件
代码示例:
function fetchDownload(url, options = {}) { | |
options = Object.assign({ credentials: 'include', method: 'get', headers: {} }, options); | |
return fetch(url, options).then(response => { | |
return response.blob().then(blob => { | |
if (!blob || !blob.size) return Promise.reject('empty'); | |
// 从 Content-Disposition 中获取文件名示例 | |
const cd = response.headers.get('Content-Disposition'); | |
if (cd && cd.includes('fileName') && !options.fileName) options.fileName = cd.split('fileName=')[1]; | |
options.fileName = decodeURIComponent(options.fileName || 'download-file'); | |
saveAs(blob, options.fileName); | |
return options.fileName; | |
}); | |
}); | |
} | |
// 测试 | |
fetchDownload('https://lzw.me/images/avatar/lzwme-80x80.png', { | |
// method: 'post', | |
// headers: { | |
// 'Content-Type': 'application/json' | |
// }, | |
// body: JSON.stringify({ | |
// pageSize: 100000, | |
// startPage: 0 | |
// }) | |
}) | |
.then(fileName => console.log('文件下载完成:', fileName)) | |
.catch(err => console.log('文件下载失败', err)); |
5. 相关参考
- data URIs https://developer.mozilla.org/zh-CN/docs/Web/HTTP/data_URIs
- HTMLCanvasElement.toDataURL https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL
- http://www.alloyteam.com/2014/01/use-js-file-download/
相关文章:











