这应该是你见过的最全前端下载总结自己整理的一些项目中遇到过的关于上传和下载的一些 Demo,大前端系列(也就是纯前端 + node 端完成的下载,只要获取到数据下载工作全是前端来做),仅供给位看官参考,避免踩坑,即插即用,欢迎 fork 和 star????,为这个仓库添砖加瓦~(P.S. 个人认为如果没写过上传下载其实还是挺麻烦的,这个基本能覆盖大部分场景了~)
包括内容
纯前端下载
基于 a 标签
location && iframe
FileSaver —> [推荐]
node 端下载
先下载到本地,再下载到浏览器
直接流向浏览器下载 [推荐]
在这里怕大家没有耐心看下去,放一个强烈推荐的 filesaver 的 Demo 动态图
写在前面
上传和下载个人认为在前端开发时稍微复杂一丢丢,需要额外处理一些事情而不是直接获取数据渲染页面,所以想着把平时遇到过的一些场景整理一下分享出来,大牛绕过,不喜勿喷~我平时在项目中接触的也就是一些上传图片,上传安装包,下载图片,下载安装包以及整理数据生成 excel 文件下载下来。暂时还没有接触过其他类型的,所以本项目可能有一定的局限性,只是给大家提供一种思路或者方案,有其他想法欢迎评论~
纯前端下载形式
顾名思义,纯前端实现,也就是不依赖于任何后端。不过这种方式有一定的局限性,比如下载类型,写法,数据形式等等。但是既然不依赖与后端,在可接受范围之内还是很推荐使用的,毕竟简单啊~
基于 a 标签下载
说到简单,那么最简单的就是这个了。那就是基于 <a> 标签的下载文件方式,真的是超级简单。使用方法如下:
href: 文件的绝对 / 相对地址
download: 文件名(可省略,省略后浏览器自动识别源文件名)
<a href=’xxx.jpg’ download=’file.jpg’> 下载 jpg 图片 </a>
那么既然这么简单,那肯定是存在问题的。
上面这张图片是官方提供的兼容性,目前只有 FireFox 和 Chrome 支持 download 属性。至少这两个对于开发者来说不陌生,占有量也很大,所以也还可以吧,但是接下来我又尝试了一下这两个浏览器的兼容性情况。上面这张,是 FireFox 浏览器最新版,可以看到点击下载文件会弹出一个对话框,之后点击保存文件才可以进行下载,同时只能下载不能被浏览器打开的文件类型,如图片、文本文件、html 文件这种可以被打开的文件,是无法被下载的直接在浏览器进行预览。上面这张,是 Chrome 最新版,与 FireFox 相同,对于图片文件和文本文件这种可以被浏览器打开的文件不会被下载,而 excel 和安装包这种文件是可以被直接下载的,无需进行任何二次确认操作。
那么能不能不让浏览器预览图片(或 pdf 或 txt 文件)?
肯定能啊~为什么呢?其实 a 标签的 href 属性还可以接受除了相对和绝对路径之外的其他形式 Url,比如下面我们要用到的 DataUrl 和 BlobUrl。我们使用这种形式,就可以让浏览器不预览而直接下载图片了,当然了操作起来更麻烦一些了就。
DataUrl
// 首先,图片转 base64
// ./util.js
// html 页面,将 a 标签 href 属性动态赋值为 dataUrl
<a id=’downloadDataUrl’ class=”button is-dark”> 下载 data:Url 图片 </a>
…
<script>
const image = new Image();
image.setAttribute(“crossOrigin”,’Anonymous’);
image.src = ‘../files/test-download.png’ + ‘?’ + new Date().getTime();
image.onload = function() {
const imageDataUrl = image2base64(image);
const downloadDataUrlDom = document.getElementById(‘downloadDataUrl’);
downloadDataUrlDom.setAttribute(‘href’, imageDataUrl);
downloadDataUrlDom.setAttribute(‘download’, ‘download-data-url.png’);
downloadDataUrlDom.addEventListener(‘click’, () => {
console.log(‘ 下载文件 ’);
});
}
</script>
如下图,可以看到不再是预览文件,而是直接下载文件了。这里面有一些坑,比如 canvas.toDataUrl 的一些问题以及解决办法,我就不多说了,大家自己去看看。
BlobUrl
整体逻辑更复杂了,首先 文件 -> base64(dataUrl) -> blob -> blobUrl
// 第一步:首先需要将文件转换成 base64,方法上面一样
// 第二步:将 base64 转换成 blob 数据
// DataUrl 转 Blob 数据
function dataUrl2Blob(dataUrl) {
var arr = dataUrl.split(‘,’),
mime = arr[0].match(/:(.*?);/)[1],
bStr = atob(arr[1]),
n = bStr.length,
unit8Array = new Uint8Array(n);
while (n–) {
unit8Array[n] = bStr.charCodeAt(n);
}
return new Blob([unit8Array], {type: mime});
}
// 第三步:将 blob 数据转换成 BlobUrl
URL.createObjectURL(imageBlobData);
// 完整代码
<a id=’downloadBlobUrl’ class=”button is-danger”> 下载 blobUrl 图片 </a>
…
const image2 = new Image();
image2.setAttribute(“crossOrigin”,’Anonymous’);
image2.src = ‘../files/test-download.png’ + ‘?’ + new Date().getTime();
image2.onload = function() {
const imageDataUrl = image2base64(image2);
const imageBlobData = dataUrl2Blob(imageDataUrl);
const downloadDataUrlDom = document.getElementById(‘downloadBlobUrl’);
downloadDataUrlDom.setAttribute(‘href’, URL.createObjectURL(imageBlobData));
downloadDataUrlDom.setAttribute(‘download’, ‘download-data-url.png’);
downloadDataUrlDom.addEventListener(‘click’, () => {
console.log(‘ 下载文件 ’);
});
}
【总结】: Chrome 在兼容性上更胜一筹,但是二者总体来说都存在一些问题,不能直接下载图片和文本文件,但是毕竟这么简洁,你没进行任何多余的操作,存在问题合情合理。同时,上面的几种方式也看到了,dataUrl 适合图片的下载,而 blobUrl 虽然要麻烦一些,但是对于文本文件的下载还是非常有用的,你可以直接把要下载的内容转换成 blob 数据,然后转换成 blobUrl 进行下载,适用于.txt,.json 等文件类型。【建议】: 如果下载的需求是特殊文件类型,如安装包,excel 文件,并且可以存放在 CDN 又一个可访问的 url 链接。那么这种方式非常完美,当然,如果你可以接受上面所说的兼容性问题。同时如果你采用 dataUrl 或者 blobUrl 的时候,由于存在很多问题,比如 cors 之类的事情,建议可以使用这种方法,但是需要配合后端,也就是后端帮你转换好,你直接拿转换好的 url 来下载就行了。
location.href 和 iframe 下载
上面这两种非常好理解,就是在另一个窗口或者当前地址栏地址指向下载链接,下载链接要求是 dataUrl 或者 blobUrl。只不过,iframe 是更高级一些,也就是可以帮助我们做到无闪下载,作为开发者大家应该都懂,我就不多 BB 了。
从上面这个动图,可以看出来,这个方法其实还不如 <a> 标签下载,为什么这么说呢,会因为 a 标签方法虽然会预览浏览器可以预览的文件,但是如果进行适当转化,还是能进行下载的。但是 location 这种方法无论是 dataUrl 还是 blobUrl,只要是图片、文本文件以及 pdf 等所有浏览器可以打开的文件,都会直接给你预览,只能下载那些浏览器不支持预览的那些文件。所以 Just so so 了。
iframe 封装无闪现下载方法
本质很简单,就是不让当前浏览器窗口执行下载操作,而是另开一个 iframe 进行文件的下载。但是这个 iframe 是用户不可见的~ 这里需要注意,如果是纯前端,建议不要进行图片等浏览器可打开的文件下载,因为隐藏 iframe 里打开你也看不到,也就是他的问题还是上面那些。可以进行 excel、zip 以及各种资源文件的下载。
// 无闪现下载 excel
function download(url) {
const iframe = document.createElement(‘iframe’);
iframe.style.display = ‘none’;
function iframeLoad() {
console.log(‘iframe onload’);
const win = iframe.contentWindow;
const doc = win.document;
if (win.location.href === url) {
if (doc.body.childNodes.length > 0) {
// response is error
}
iframe.parentNode.removeChild(iframe);
}
}
if (‘onload’ in iframe) {
iframe.onload = iframeLoad;
} else if (iframe.attachEvent) {
iframe.attachEvent(‘onload’, iframeLoad);
} else {
iframe.onreadystatechange = function onreadystatechange() {
if (iframe.readyState === ‘complete’) {
iframeLoad;
}
};
}
iframe.src = ”;
document.body.appendChild(iframe);
setTimeout(function loadUrl() {
iframe.contentWindow.location.href = url;
}, 50);
}
如果你在项目里需要进行无闪现下载,什么都不用做,只需要调用 download(url),即可进行无闪现下载~亲测可用
使用 FileSaver 强大的前端下载插件 ->【强烈推荐】
FileSaver 的下载方式完全是前端 (Client-Side) 的下载方式,它是基于 Blob 进行下载的,当然因为是基于前端下载,所以浏览器下载会有一定的限制,也就是 Blob 数据的大小不能过大,看看官网给的相关参数:
可以看到,基本对于支持的浏览器来说,大小可以达到 500MB+,应该已经可以满足大部分需求了。如果文件确实很大,官网给出了替代方案 StreamSaver,没去研究过这个,不过作者既然推荐可能也很好,感兴趣的可以去看看。
前面讲到了,FileSaver 是基于 Blob 的,其实并不准确,可以看一下官网:
其实它支持 Blob、File 和 Url 进行下载,但是如果基于 url 了我也没必要用 FileSaver 了,那个 <a> 标签也挺好的是不?然后基于 File 一般都是特定场景,比如上传的时候,才会用到 FileReader 之类的 API,说实话我也没怎么用过,都是封装的,所以这里也不做介绍。开头也说过了,希望小伙伴可以给这个仓库添加东西啊,可以增加自己的下载 Demo 到这里,非常欢迎
所以我这篇文章讨论的下载,就是基于 Blob。首要工作就是将文件转换成 Blob 数据。下面几个例子都是这样:
FileSaver —- 下载 canvas
依赖 – canvas-to-blob
这个 Demo 简单点的话其实可以直接用 canvas 画一个 image 在页面上,然后再进行下载,但是那样还不如直接下载图片了,所以麻烦一些,写一个 canvas 白板,然后下载我们自己绘制的内容并且起名字进行下载。
// 生成下载的文件名
function generateFilename(id, mime) {
const filename = document.getElementById(id).value || document.getElementById(id).placeholder;
return filename + mime;
}
const canvasDownloadDom = document.getElementById(‘download-canvas’);
canvasDownloadDom.addEventListener(‘click’, () => {
const canvas = document.getElementById(‘canvas’);
const filename = generateFilename(‘canvasName’, ‘.png’);
if (canvas.toBlob) {
// 调用方法将 canvas 转换成 blob 数据
canvas.toBlob(
function (blob) {
// 调用 FileSaver 方法下载
saveAs(blob, filename);
},
‘image/png’
);
}
});
代码非常简单,感兴趣的小伙伴可以去看看每个插件内部的代码。我这里就是应用级别的示例了。
FileSaver —- 直接下载图片
直接下载图片就是将图片转换成 Blob 数据,然后进行下载。
// FileSaver 下载文件
const image = new Image();
image.setAttribute(“crossOrigin”,’Anonymous’);
image.src = ‘../files/test-download.png’ + ‘?’ + new Date().getTime();
image.onload = function() {
const imageDataUrl = image2base64(image);
const imageBlobData = dataUrl2Blob(imageDataUrl);
const downloadImageDom = document.getElementById(‘download-image’);
downloadImageDom.addEventListener(‘click’, () => {
saveAs(imageBlobData, ‘test-download.png’);
});
}
这代码就更简单了,就是前面 <a> 标签下载 Blob 数据的代码,数据转换是一样的,只不过下载使用的是 FileSaver。
FileSaver —- 下载文本文件
下载文本文件就更容易了,因为 JavaScript 支持直接将字符串构造成 Blob 对象。
const textBlob = new Blob([“your target string”], {type: “text/plain;charset=utf-8”});
下载下来的 txt 文件长这样:
// FileSaver 下载文本文件
const txtDownloadDom = document.getElementById(‘download-txt’);
txtDownloadDom.addEventListener(‘click’, () => {
const textarea = document.getElementById(‘textarea’);
const filename = generateFilename(‘textareaName’, ‘.txt’);
const textBlob = new Blob([textarea.value], {type: “text/plain;charset=utf-8”});
saveAs(textBlob, filename);
});
FileSaver —- 下载 Excel 文件(搭配 js-xlsx)
前面的都相对简单一些,但是其实除了下载图片,可能平时也没什么业务场景需要到。接下来要说的可是所有商务系统几乎都能遇到的了,那就是 —— 下载报表,也就是 Excel 文件。这里面就使用 FileSaver 配合 js-xlsx 来进行 excel 的纯前端下载工作~
下载下来的文件长这样:
// 下载 excel 文件
const excelDownloadDom = document.getElementById(‘download-excel’);
excelDownloadDom.addEventListener(‘click’, () => {
// 找到 table 节点调用方法转化数据
const wb = XLSX.utils.table_to_book(document.querySelector(‘#table-excel’));
// 生成 excel 数据
const wbout = XLSX.write(wb, { bookType: ‘xlsx’, bookSST: true, type: ‘array’});
try {
// 下载 excel 文件
saveAs(new Blob([wbout], {type: ‘application/octet-stream’}), ‘table-excel.xlsx’);
} catch (e) {
if (typeof console !== ‘undefined’) console.log(e, wbout)
}
});
这里面我只是介绍如何用 FileSaver 在前端下载 excel 文件,至于 js-xlsx 如何将数据转化成 excel 的这里不做介绍。我只是简单的调用了 js-xlsx 的将 table 转成 excel 的方法,js-xlsx 还有很多高级功能,有这方面需求的去看看官方文档 js-xlsx 就好了~
node 端配合下载(大前端)
带后端支持的下载就要轻松很多了,为什么呢,因为上面所有纯前端下载都可以与后端进行配合使用,也就是后端生成对应的下载链接下载数据返回给前端,前端根据设计方案按需使用上面几种方式下载,肯定能下载成功。那么 node 端配合下载肯定是要下载点不一样的东西了——那就是文件流。
有很多场景,那就是大文件不是存在于 CDN,而是以文件流的形式存放在内存。那么就没有对应的下载链接,下载对应文件的时候,后端返回的就是文件流。而 node 里为我们提供 Stream 支持各种流操纵。所以我们可以在 node 端直接进行文件的下载。
先下载到本地在从浏览器下载
fs 下载 Excel
下载下来的 Excel 文件:
// 第一步:构造数据
const data = [
[1, 2, 3],
[true, false, null, ‘sheetjs’],
[‘foo’, ‘bar’, new Date(‘2014-02-19T14:30Z’), ‘0.3’],
[‘baz’, null, ‘qux’],
];
// 第二步:生成 excel 的 Buffer 数据
const buffer = xlsx.build([{name: ‘mySheetName’, data}]);
// 第三步:写文件到本地
const tmpExcel = `filename.xlsx`;
fs.writeFileSync(
tmpExcel,
buffer,
{
encoding: ‘utf8’,
},
err => {
if (err) throw new Error(err);
},
);
// 第四步:从本地读取文件下载到浏览器
res.setHeader(‘Content-disposition’, `attachment; filename=”${tmpExcel}”`);
res.setHeader(‘Content-Type’, ‘application/octet-stream’);
// pipe generated file to the response
fs.createReadStream(tmpExcel).pipe(res);
// 下载完成后删除文件
fs.unlinkSync(tmpExcel);
下载 excel 在 node 端我使用的不是 js-xlsx 而是 node-xlsx,因为它构造数据非常简单,功能也很强大,十分推荐大家使用~
fs 下载文件
这里场景不是很容易描述,因为 Demo 我都是将文件放到本地目录的,所以我读取本地文件再下载到本地再下载到浏览器,我这不是有病吗。。。一般场景是文件以文件流的形式存在内存里,然后我们通过接口下载到本地再从本地下载到浏览器。或者是上传文件保存到本地,然后在从本地进行相关操作,这里就不写示例代码了。
node 端直接流向浏览器下载【推荐】
node 端,我使用的是 express 框架(其他的框架也都一样),如果你是文件流直接过来的,那么直接调用 res.attachment()下载文件流,如果是文件 path,那么可以直接 res.download(filepath)。具体见 demo
直接下载 Excel
上面过程其实多经历了一步,为什么呢?因为拿到 buffer 之后我们其实就可以直接将 buffer 流向浏览器下载了~这里我用的是 Express 框架,直接使用 res.attachment()方法就可以了。下载下来的文件与上面一模一样我就不展示了。
按照我的理解,第二种明显要比第一种好很多为什么还要列出第一种呢?我个人觉得,第一种虽然一定会牺牲一定的性能,但是先下载到本地就可以对文件进行一些校验,比如文件是否完整,文件名之类的是否合法,还有些时候的场景可能。毕竟不是所有的下载场景都像 Demo 这样简单。存在即合理,所以还是都罗列出来。
// 第一步:构造数据
const data = [
[1, 2, 3],
[true, false, null, ‘sheetjs’],
[‘foo’, ‘bar’, new Date(‘2014-02-19T14:30Z’), ‘0.3’],
[‘baz’, null, ‘qux’],
];
// 第二步:生成 buffer
const buffer = xlsx.build([{name: ‘mySheetName’, data}]);
// 第三步:直接下载
res.status(200)
.attachment(‘bufferExcel.xlsx’)
.send(buffer);
// 上面下载代码等同于下面这段代码(nodejs 原生代码)
res.setHeader(‘Content-disposition’, `attachment; filename=”${tmpExcel}”`);
res.setHeader(‘Content-Type’, ‘application/octet-stream’);
res.end(buffer);
直接下载文件流
这里我把文件安装包放在本地了,然后我先读取文件内容同时下载到浏览器~
// 第一种,已知文件路径直接下载
try {
const packagePath = ‘static/download/iTerm2-3_2_5.zip’;
res.download(path.join(rootDir, packagePath));
} catch (e) {
console.error(e);
}
// 第二种,读取本地文件流向浏览器
res.setHeader(‘Content-disposition’, `attachment; filename=”download-package.zip”`);
res.setHeader(‘Content-Type’, ‘application/octet-stream’);
fs.createReadStream(path.join(rootDir, packagePath), ‘utf-8’).pipe(res);
request
最后给大家安利一个将 Stream API 使用到极致的 Http(Https)请求库 —— request。
// 不加这一行下载下来的文件没有后缀
res.setHeader(‘Content-disposition’, ‘attachment; filename=node-v8.14.0-linux-x64.tar.gz’);
request(‘https://npm.taobao.org/mirrors/node/v8.14.0/node-v8.14.0-linux-x64.tar.gz’)
.pipe(res);
文章来源:frontend-download-sample