本身整理的一些项目中遇到过的关于上传和下载的一些Demo,大前端系列(也就是纯前端 + node端完成的下载,只要获取到数据下载工做全是前端来作),仅供给位看官参考,避免踩坑,即插即用,欢迎fork和star🌟,为这个仓库添砖加瓦~(P.S. 我的认为若是没写过上传下载其实仍是挺麻烦的,这个基本能覆盖大部分场景了~)html
=======> frontend-download-sample前端
在这里怕你们没有耐心看下去,放一个强烈推荐的filesaver的Demo动态图node
上传和下载我的认为在前端开发时稍微复杂一丢丢,须要额外处理一些事情而不是直接获取数据渲染页面,因此想着把平时遇到过的一些场景整理一下分享出来,大牛绕过,不喜勿喷~我平时在项目中接触的也就是一些上传图片,上传安装包,下载图片,下载安装包以及整理数据生成excel文件下载下来。暂时尚未接触过其余类型的,因此本项目可能有必定的局限性,只是给你们提供一种思路或者方案,有其余想法欢迎评论~linux
顾名思义,纯前端实现,也就是不依赖于任何后端。不过这种方式有必定的局限性,好比下载类型,写法,数据形式等等。可是既然不依赖与后端,在可接受范围以内仍是很推荐使用的,毕竟简单啊~git
说到简单,那么最简单的就是这个了。那就是基于<a>
标签的下载文件方式,真的是超级简单。使用方法以下:github
href: 文件的绝对/相对地址
download: 文件名(可省略,省略后浏览器自动识别源文件名)
<a href='xxx.jpg' download='file.jpg'>下载jpg图片</a>
复制代码
那么既然这么简单,那确定是存在问题的。express
确定能啊~为何呢?其实a标签的href属性还能够接受除了相对和绝对路径以外的其余形式Url,好比下面咱们要用到的DataUrl和BlobUrl。咱们使用这种形式,就可让浏览器不预览而直接下载图片了,固然了操做起来更麻烦一些了就。npm
// ./util.js
// 图片转base64
function image2base64(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const mime = img.src.substring(img.src.lastIndexOf(".")+1).toLowerCase();
const dataUrl = canvas.toDataURL("image/" + mime);
return dataUrl;
}
// 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的一些问题以及解决办法,我就很少说了,你们本身去看看。 json
// 第一步:首先须要将文件转换成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等文件类型。canvas
更新于2019-09-11。今天发现最新Chrome/Firefox浏览器支持一些预览文件的下载了,好比图片、pdf和txt文件,就是经过a标签能够直接下载~
【建议】: 若是下载的需求是特殊文件类型,如安装包,excel文件,而且能够存放在CDN又一个可访问的url连接。那么这种方式很是完美,固然,若是你能够接受上面所说的兼容性问题。同时若是你采用dataUrl或者blobUrl的时候,因为存在不少问题,好比cors之类的事情,建议可使用这种方法,可是须要配合后端,也就是后端帮你转换好,你直接拿转换好的url来下载就好了。
上面这两种很是好理解,就是在另外一个窗口或者当前地址栏地址指向下载连接,下载连接要求是dataUrl或者blobUrl。只不过,iframe是更高级一些,也就是能够帮助咱们作到无闪下载,做为开发者你们应该都懂,我就很少BB了。
<a>
标签下载,为何这么说呢,会由于a标签方法虽然会预览浏览器能够预览的文件,可是若是进行适当转化,仍是能进行下载的。可是location这种方法不管是dataUrl仍是blobUrl,只要是图片、文本文件以及pdf等全部浏览器能够打开的文件,都会直接给你预览,只能下载那些浏览器不支持预览的那些文件。因此Just so so了。
本质很简单,就是不让当前浏览器窗口执行下载操做,而是另开一个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的下载方式彻底是前端(Client-Side)的下载方式,它是基于Blob进行下载的,固然由于是基于前端下载,因此浏览器下载会有必定的限制,也就是Blob数据的大小不能过大,看看官网给的相关参数:
能够看到,基本对于支持的浏览器来讲,大小能够达到 500MB+,应该已经能够知足大部分需求了。若是文件确实很大,官网给出了替代方案StreamSaver,没去研究过这个,不过做者既然推荐可能也很好,感兴趣的能够去看看。
前面讲到了,FileSaver是基于Blob的,其实并不许确,能够看一下官网:
<a>
标签也挺好的是不?而后基于File通常都是特定场景,好比上传的时候,才会用到FileReader之类的API,说实话我也没怎么用过,都是封装的,因此这里也不作介绍。
开头也说过了,但愿小伙伴能够给这个仓库添加东西啊,能够增长本身的下载Demo到这里,很是欢迎
因此我这篇文章讨论的下载,就是基于Blob。首要工做就是将文件转换成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'
);
}
});
复制代码
代码很是简单,感兴趣的小伙伴能够去看看每一个插件内部的代码。我这里就是应用级别的示例了。
直接下载图片就是将图片转换成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。
下载文本文件就更容易了,由于JavaScript支持直接将字符串构形成Blob对象。
const textBlob = new Blob(["your target string"], {type: "text/plain;charset=utf-8"});
复制代码
// 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);
});
复制代码
前面的都相对简单一些,可是其实除了下载图片,可能平时也没什么业务场景须要到。接下来要说的但是全部商务系统几乎都能遇到的了,那就是 —— 下载报表,也就是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端配合下载确定是要下载点不同的东西了——那就是文件流。
有不少场景,那就是大文件不是存在于CDN,而是以文件流的形式存放在内存。那么就没有对应的下载连接,下载对应文件的时候,后端返回的就是文件流。而node里为咱们提供
Stream
支持各类流操纵。因此咱们能够在node端直接进行文件的下载。
// 第一步:构造数据
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,由于它构造数据很是简单,功能也很强大,十分推荐你们使用~
这里场景不是很容易描述,由于Demo我都是将文件放到本地目录的,因此我读取本地文件再下载到本地再下载到浏览器,我这不是有病吗。。。通常场景是文件以文件流的形式存在内存里,而后咱们经过接口下载到本地再从本地下载到浏览器。或者是上传文件保存到本地,而后在从本地进行相关操做,这里就不写示例代码了。
node端,我使用的是express框架(其余的框架也都同样),若是你是文件流直接过来的,那么直接调用res.attachment()
下载文件流,若是是文件path,那么能够直接res.download(filepath)
。具体见demo
上面过程其实多经历了一步,为何呢?由于拿到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);
复制代码
最后给你们安利一个将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);
复制代码
这里我为了省时间,就用了本身之前搭过的一个脚手架Next-Antd-Scafflod,直接在这里写的Demo。你能够理解我在打广告,你点进去给个star我也不介意😄。
看到这里不管怎么样都十分感谢了,总结的不怎么样,能够理解为自我总结文章吧。若是对你们能有那么一点点启发那就更好了。最后若是有兴趣或者有疑问,能够留言或者直接去仓库里提~
Star🌟地址:frontend-download-sample