前端下载利器FileSaver

本文介绍了前端下载的方法,包括利用 `location.href` 和 `iframe`,以及使用 `FileSaver.js` 进行前端下载。重点讲述了 `FileSaver.js` 的优势和限制,提供了下载图片、文本文件和Excel文件的示例。建议在文件大小适中或配合后端处理的情况下使用前端下载。同时提到了Node.js端如何配合前端进行大文件流下载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:想必很多小伙伴都用过 a标签拿来下载的吧,里面很多坑相信也知道了,用起来真的是超级简单。使用方法如下:

href: 文件的绝对/相对地址

download: 文件名(可省略,省略后浏览器自动识别源文件名)

<a href='xxx.jpg' download='file.jpg'>下载jpg图片</a>

那么既然这么简单,那肯定是存在问题的。

图片

上面这张图片是官方提供的兼容性,目前只有FireFox和Chrome支持download属性。对于图片文件和文本文件这种可以被浏览器打开的文件不会被下载,而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数据
    	<script type="text/javascript">
    		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('下载文件');
    			});
    		}
    	</script>
    

【总结】: Chrome在兼容性上更胜一筹,但是二者总体来说都存在一些问题,不能直接下载图片和文本文件,但是毕竟这么简洁,你没进行任何多余的操作,存在问题合情合理。同时,上面的几种方式也看到了,dataUrl适合图片的下载,而blobUrl虽然要麻烦一些,但是对于文本文件的下载还是非常有用的,你可以直接把要下载的内容转换成blob数据,然后转换成blobUrl进行下载,适用于.txt,.json等文件类型

【建议】: 如果下载的需求是特殊文件类型,如安装包,excel文件,并且可以存放在CDN又一个可访问的url链接。那么这种方式非常完美,当然,如果你可以接受上面所说的兼容性问题。同时如果你采用dataUrl或者blobUrl的时候,由于存在很多问题,比如cors之类的事情,建议可以使用这种方法,但是需要配合后端,也就是后端帮你转换好,你直接拿转换好的url来下载就行了。

location.href 和 iframe下载

上面这两种非常好理解,就是在另一个窗口或者当前地址栏地址指向下载链接,下载链接要求是dataUrl或者blobUrl。只不过,iframe是更高级一些,也就是可以帮助我们做到无闪下载,作为开发者大家应该都懂,我就不多BB了。

使用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);

这里我为了省时间,就用了一个脚手架Next-Antd-Scafflod,直接在这里写的Demo。

感谢:https://mp.weixin.qq.com/s/ort9FtIVylmXKfJ6kDuOVg

frontend-download-sample :https://github.com/luffyZh/frontend-download-sample

FileSaver:https://github.com/eligrey/FileSaver.js

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值