在 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);
a.click();}
阐明:
href
属性设置要下载的文件地址。这个地址反对多种形式的格局,因而能够实现丰盛的下载办法。download
属性设置了下载文件的名称。但href
属性为一般链接并且跨域时,该属性值设置少数状况下会被浏览器疏忽。
1.1 一般连贯下载示例
// 下载图片
download('https://lzw.me/images/gravatar.gif', 'lzwme-gravatar');
// 下载一个连贯
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.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
<img src=”https://lzw.me/wp-content/uploads/2017/04/a-download.png” alt=”” width=”879″ height=”346″ class=”aligncenter size-full wp-image-2330″ />
判断浏览器是否反对该属性,只须要检测 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!')
}
})
在 serviceWorker 中,截拦附带 isDownload 头信息的申请,结构下载回应:
self.addEventListener('fetch', function(event) {
const req = event.request;
if (!req.headers.get('isDownload')) {retrun fetch(req);
}
const filename = encodeURIComponent(req.url);
const contentType = req.headers.get('Content-Type') || 'application/force-download';
const disposition = "inline;filename=" + filename + ";filename*=utf-8''" + filename
const myBody = req.headers.get(body).data;
event.respondWith(
new Response(myBody, {
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));