JavaScript实现多文件下载方法解析


Posted in Javascript onAugust 07, 2020

对于文件的下载,可以说是一个十分常见的话题,前端的很多项目中都会有这样的需求,比如 highChart 统计图的导出,在线图片编辑中的图片保存,在线代码编辑的代码导出等等。而很多时候,我们只给了一个链接,用户需要右键点击链接,然后选择“另存为”,这个过程虽说不麻烦,但还是需要两步操作,倘若用户想保存页面中的多个链接文件,就得重复操作很多次,最常见的就是英语听力网站上的音频下载,手都要点麻!

本文的目的是介绍如何利用 javascript 进行多文件的下载,也就是当用户点击某个链接或者按钮的时候,同时下载多个文件。这里的“同时”用的不是很准确,在现代浏览器中可以实现多文件的并行下载,而在一些老版本浏览器,如IE8-,此类的浏览器就只能进行单个文件的下载,但是我们可以让多个文件依次保存下来,算是串行下载吧~

若要要无视实现细节,可以直接跳到第三部分,或者戳:

代码封装:lib.js

DEMO:javascript-multiple-download(HTTPS,第三个有bug,具体原因下面有说明)

javascript-multiple-download(HTTP,测试正常)

一、文件类型介绍及其特点

1. 一般类型

平时比较常见的有 txt、png、jpg、zip、tar 等各种文件格式,这些文件格式中,一部分浏览器是会直接打开链接显示内容的,而另外一部分,浏览器不识别响应头,或者不能解析对应的格式,于是当做文件直接下载下来了。如:

<a href="http://barretlee.com/test.rar" rel="external nofollow" >file</a>

这句代码,若直接点开链接,浏览器将会直接下载该文件。

2. dataURL类型

dataURL 也是十分常见的类型,他可以作为 src 或者 url() 的参数送进去。比较常见的有如下几种:

文本: data:text/plain;这里是正文内容。
图片: data:image/jpg;base64,/9j/4AAQSkZJRgABAQEA....
  data:image/png;base64,/9j/4AAQSkZJRgABAQEA....

base64 是用的比较广泛的一种数据格式。

Base64格式
data:[][;charset=][;base64],

Base64 在CSS中的使用:
.demoImg{ background-image: url("data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...."); }

Base64 在HTML中的使用:
<img width="40" height="30" src="data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...." />

3. Blob 流

Blob 对象表示不可变的、包含原始数据的类文件对象。具体的内容可以参阅MDN文档。

他的使用也是特别的方便,如:

var aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // the blob

Blob 接收两个参数,一个是数组类型的数据对象,他可以是 ArrayBuffer、ArrayBufferView、Blob、String 等诸多类型;第二个参数是 MINE 类型设置。而本文我们要用到的是 URLcreateObjectURL() 这个函数,他的作用是将一个 URL 所代表的内容转化成一个DOMString,产生的结果是一个 文件对象 或者 Blob 对象。

4. 二进制流

我们利用 File API 读取文件的时候,拿到的是数据的二进制流格式,这些类型可以直接被 ArrayBuffer 等接收,本文中没有用到,就不细说了。

二、JavaScript 多文件下载

HTML5 中 a 标签多了一个属性——download,用户点击链接浏览器会打开并显示该链接的内容,若在链接中加了 download 属性,点击该链接不会打开这个文件,而是直接下载。虽说是比较好用,但低版本浏览器不兼容,这个在本节的 2 和 3 中将会讲到解决方案。

在这里,我们可以利用属性检测UA 来判断浏览器类型:

h5Down = document.createElement("a").hasOwnProperty("download");
var h5Down = !/Trident|MSIE/.test(navigator.userAgent); // Trident 标识 IE11

1. a 标签 download 属性的使用

注:FF5.0 / Safari5.0 / Opera11.1 / IE9.0 不支持 download 属性

利用 download 属性可以直接下载单个文件,若想点击一次下载多个文件,就得稍加处理下了:

function downloadFile(fileName, content){
 var aLink = document.createElement("a"),
  evt = document.createEvent("HTMLEvents");

 evt.initEvent("click");
 aLink.download = fileName;
 aLink.href = content;

 aLink.dispatchEvent(evt);
}

download 属性的作用除了让浏览器忽略文件的 MIME 类型之外,还会把该属性的值作为文件名。你可以在 chrome 控制台运行这句程序:

downloadFile("barretlee.html", "./");

浏览器会提示是否保留(下载)该 html 文件。之前我们提到文件类型还可能是 dataURL 或者是 Blob 流,为了让程序也支持这些数据类型,稍微修改下上面的函数:

function downloadFile(fileName, content){
 var aLink = document.createElement('a');
  , blob = new Blob([content])
  , evt = document.createEvent("HTMLEvents");

 evt.initEvent("click");

 aLink.download = fileName;
 aLink.href = URL.createObjectURL(blob);
 aLink.dispatchEvent(evt);
}

new Blob([content]),现将文件转换成一个 Blog 流,然后,使用 URL.createObjectURL() 将其转换成一个 DOMString。这样我们就支持 data64 和其他数据类型的 content 了~

2. window.open 之后 execCommand("SaveAs")

上面也提到了,尽管 download 属性是十分便利的 H5 利器,但低版本 IE 根本不赏脸,要说方法,IE 还是有很多方式去转换的,比如 ADOBE.STREAM 的 activeX 对象可以把文件转换成文件流,然后写入到一个要保存的文件中。这里要谈到的是略微方便一点的方式:先把内容写到一个新开的 window 对象中,然后利用 execCommand 执行保存命令,就相当于我们在页面上按下 Ctrl+S,这样页面内的信息都会 down 下来。

// 将文件在一个 window 窗口中打开,并隐藏这个窗口。
var win = window.open("path/to/file.ext", "new Window", "width=0,height=0");
// 在 win 窗口中按下 ctrl+s 保存窗口内容
win.document.execCommand("SaveAs", true, "filename.ext");
// 使用完了,关闭窗口
win.close();

这个过程十分明了,不过这里会存在一个问题,并不是程序的问题,而是浏览器的问题,如果我们用 搜狗浏览器 或者 360浏览器 打开新窗口的话,他会新开一个标签页,而不是新开一个窗口,更可恶的是部分浏览器拦截 window.open 的窗口(这个可以设置)。所以只好另觅他法了。

3. iframe 中操作

既然新开一个窗口那么麻烦,我就在本窗口下完成工作~

function IEdownloadFile(fileName, contentOrPath){
 var ifr = document.createElement('iframe');

 ifr.style.display = 'none';
 ifr.src = contentOrPath;

 document.body.appendChild(ifr);


 // 保存页面 -> 保存文件
 ifr.contentWindow.document.execCommand('SaveAs', false, fileName);
 document.body.removeChild(ifr);
}

一般的链接我们可以直接给 iframe 添加 src 属性,然后执行 saveAs 命令,倘若我们使用的是 data64 编码的文件,这个怎么办?

var isImg = contentOrPath.slice(0, 10) === "data:image";

// dataURL 的情况
isImg && ifr.contentWindow.document.write("<img src='" + 
  contentOrPath + "' />");

这个也比较好处理,直接把文件写入到 iframe 中,然后在执行保存。

三、代码的封装与接口介绍

1. 代码的封装以及相关 DEMO

封装:lib.js

DEMO:javascript-multiple-download(HTTPS,第三个有bug)

javascript-multiple-download(HTTP,测试正常)

Bug 说明,经过一番细节处理之后,基本兼容各个浏览器,我把代码放在 https://raw.github.com 上托管,可能因为是 https 传输,第三个测试中报错了,报错的具体内容是:HTTPS 安全受到 http://rawgithub.com/barretlee/javascript-multiple-download/master/file/test.jpg 的威胁,而 test.txt 文件没有报错。放到 http 协议下测试运行结果是可观的。(这点我没有去深究,肯定是有深层安全方面原因的,难道就因为他是 jpg图片格式? 谢@屈屈提醒,跨协议传输存在安全问题)后面的 demo 我放在 BAE 上,没有问题,不过没测试 safari 和 opera。

2. 接口的调用

提供了三个接口,支持单文件下载,多文件下载,多文件下载自定义命名。

1)单文件下载

Downer("./file/test.txt");

2)多文件下载

Downer(["./file/test.txt","./file/test.txt"]);

3)多文件下载自定义命名

Downer({
 "1.txt":"./file/test.txt",
 "2.jpg":"./file/test.jpg"
});

文件的 URL 如 ./file/test.jpg 都可以改成 base64 或者其他格式,如:

Downer({
  //这是一个很长的 dataURI,我用负的text-indent隐藏了,可直接复制
 "data64.jpg" : "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAAYADsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9NgKKK8w8beO/EMVzGdJ08W2jW2t2WnXGpLdx+e7PPEkii3eJgYj5mwsJFkzkquAC3VFc0lFf10/UOlz1CivO9H+LEmreP7jw8mlBrLfPDa6nC1y0cksX30Z2t1hGCHBEcsjAqQVHOJPCfjnxFc+HNU1rxPpmh6Vp9mt232i01WWXmGV0IdXt0CrhD8+45xnaM4A00uZ9rgtZcq3vb5noFLXCfDz4j3vjbT9XM+hSabqdhtItWFxGswZCybTc28DjJDAkx7fRjzi/qmv6zH8LdR1m6sP+Ef12PSprprPzkufssyxMwXeBtfBA5xg0OLi2n/Vwh+8aUep1gFLXGeKhqmj+FtGmt9fvvtVvfWMc9w0VsWvUkuI43WUeVtAIcnMYQggYI6Hs6lqwk7pPuNrkPEHwn8N+KNRkvdQh1BpZJY7ho7fVru3haWPbslMUcqpvXYhD7d2VU54FFFNNxd1uPyJIfhb4dt/EkOuxwXy6hBNJPD/xNLryImkz5myHzPLUNkllC4J5IyAaWP4W+GY7+/uzp7yy3sc8UiTXc0kUazHdMIo2cpDvPLeWFyeTRRRd9wH6B8NNB8NyapJZx38kmpxrFePe6pdXbTKAQMmaRjkAkZHOMDOAKmbwLp9n8Pp/CGkr/Zmm/wBnyadbjLS+QjIUB+ZstjPdsn1oopOTfUa91prdf1+hFrfhnWNb0HSLCTVrGOaC6tbi+mXT323CwyrJtiXzv3RZkXljJgZ4PWuooopNtkpWVkf/2Q==" 
});

这里只做到了 chrome 兼容,IE 下懒得去看了,这个需求很少见!

四、服务器支持与后端实现

1. 后端实现

不使用前端,直接后端实现的原理,就是在响应头中加入一些特殊的标记,如前端发送这样的请求:

function download(path) {
 var ifrm = document.getElementById(frame);
 ifrm.src = "download.php?path="+path;
}

后端的响应为

<?php 
 header("Content-Type: application/octet-stream");
 header("Content-Disposition: attachment; filename=".$_GET['path']);
 readfile($_GET['path']);
?>

告诉浏览器这是一个流文件,作为附件方式发送给你,请忽略 MINE type,直接保存。

2. 服务器配置

若后台是 apche 作为服务器,可以配置 htaccess 文件:

<filesmatch "\.(zip|rar)$"="">
Header set Content-Disposition attachment
</filesmatch>

意思是只要请求的是 zip 或者 rar 类型的文件,那么就添加一个 Content-Disposition:attachment 的响应头。这样就可以在 php 代码中省略麻烦的操作。

五、小结

由于行文仓促,文中会有不少错误,对多文件下载有更好的提议,希望提出来共同分享!

实现多文件下载的方式肯定不止上面提到的几种,而且我这里封装的代码并没有在FF safari opera 中实现,因为他们还没兼容 download 属性,具体情况可以查看 caniuse。建议在项目中把这样的事情交给后端,几句代码可以搞定。

六、参考资料

在浏览器端用JS创建和下载文件 AlloyTeam

Starting file download with Javascript Ahzaz's Blog

Blob 流 MDN

到此这篇关于JavaScript实现多文件下载方法解析的文章就介绍到这了,更多相关JavaScript多文件下载内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Javascript实现的分页函数
Dec 22 Javascript
JQuery 选择器、过滤器介绍
Feb 14 Javascript
jQuery插件开发全解析
Oct 10 Javascript
解析javascript 浏览器关闭事件
Jul 08 Javascript
js完美的div拖拽实例代码
Jan 22 Javascript
使用JavaScript判断图片是否加载完成的三种实现方式
May 04 Javascript
jquery中push()的用法(数组添加元素)
Nov 25 Javascript
值得分享的JavaScript实现图片轮播组件
Nov 21 Javascript
JS获取短信验证码倒计时的实现代码
May 22 Javascript
JS之if语句对接事件动作逻辑(详解)
Jun 28 Javascript
vue使用pdfjs显示PDF可复制的实现方法
Dec 14 Javascript
js实现左右轮播图
Jan 09 Javascript
基于javascript的无缝滚动动画1
Aug 07 #Javascript
vue-router重写push方法,解决相同路径跳转报错问题
Aug 07 #Javascript
手把手带你搭建一个node cli的方法示例
Aug 07 #Javascript
Vue两种组件类型:递归组件和动态组件的用法
Aug 06 #Javascript
vue数据更新UI不刷新显示的解决办法
Aug 06 #Javascript
基于vue 动态菜单 刷新空白问题的解决
Aug 06 #Javascript
基于JavaScript的数据结构队列动画实现示例解析
Aug 06 #Javascript
You might like
PHP简介
2006/10/09 PHP
使用apache模块rewrite_module (转)
2007/02/14 PHP
laravel框架中路由设置,路由参数和路由命名实例分析
2019/11/23 PHP
php设计模式之工厂方法模式分析【星际争霸游戏案例】
2020/01/23 PHP
PHP For循环字母A-Z当超过26个字母时输出AA,AB,AC
2020/02/16 PHP
让你的博客飘雪花超出屏幕依然看得见
2013/01/04 Javascript
javascript检测页面是否缩放的小例子
2013/05/16 Javascript
js导出table数据到excel即导出为EXCEL文档的方法
2013/10/10 Javascript
jquery ui bootstrap 实现自定义风格
2014/11/14 Javascript
JS实现图片的不间断连续滚动的简单实例
2016/06/03 Javascript
Node.js的特点详解
2017/02/03 Javascript
javascript 中null和undefined区分和比较
2017/04/19 Javascript
Bootstrap 表单验证formValidation 实现远程验证功能
2017/05/17 Javascript
详解nodejs实现本地上传图片并预览功能(express4.0+)
2017/06/28 NodeJs
react+redux的升级版todoList的实现
2017/12/18 Javascript
解决Vue2.0 watch对象属性变化监听不到的问题
2018/09/11 Javascript
微信小程序导航栏滑动定位功能示例(实现CSS3的positionsticky效果)
2019/01/24 Javascript
python继承和抽象类的实现方法
2015/01/14 Python
python文件操作之目录遍历实例分析
2015/05/20 Python
python dict.get()和dict['key']的区别详解
2016/06/30 Python
Python反射和内置方法重写操作详解
2018/08/27 Python
python多进程下实现日志记录按时间分割
2019/07/22 Python
Python读入mnist二进制图像文件并显示实例
2020/04/24 Python
Windows下pycharm安装第三方库失败(通用解决方案)
2020/09/17 Python
python中添加模块导入路径的方法
2021/02/03 Python
纯HTML5+CSS3制作图片旋转
2016/01/12 HTML / CSS
制冷与空调专业毕业生推荐信
2014/07/07 职场文书
项目申请汇报材料
2014/08/16 职场文书
群众路线教育实践活动思想汇报(2014特荐篇)
2014/09/16 职场文书
运动会加油稿100字
2014/09/19 职场文书
投标售后服务承诺书
2015/04/29 职场文书
离职信范文
2015/06/23 职场文书
国庆放假通知怎么写
2015/07/30 职场文书
浅谈golang package中init方法的多处定义及运行顺序问题
2021/05/06 Golang
html5+实现plus.io进行拍照和图片等获取
2022/06/01 HTML / CSS
js 实现Material UI点击涟漪效果示例
2022/09/23 Javascript