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比较文档位置
Apr 08 Javascript
你必须知道的Javascript知识点之&quot;单线程事件驱动&quot;的使用
Apr 23 Javascript
jQuery中unbind()方法用法实例
Jan 19 Javascript
jQuery 3.0十大新特性最终版发布
Jul 14 Javascript
KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机的方法
Oct 05 Javascript
JS版微信6.0分享接口用法分析
Oct 13 Javascript
protractor的安装与基本使用教程
Jul 07 Javascript
JS实现的简单四则运算计算器功能示例
Sep 27 Javascript
基于BootStrap的文本编辑器组件Summernote
Oct 27 Javascript
浅谈vuejs实现数据驱动视图原理
Feb 23 Javascript
ES6 中可以提升幸福度的小功能
Aug 06 Javascript
基于Vue CSR的微前端实现方案实践
May 27 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正则表达式中的非贪婪模式匹配的使用
2014/11/25 PHP
PHP 获取客户端 IP 地址的方法实例代码
2018/11/11 PHP
Eval and new funciton not the same thing
2012/12/27 Javascript
web开发人员学习jQuery的6大理由及jQuery的优势介绍
2013/01/03 Javascript
js iframe跨域访问(同主域/非同主域)分别深入介绍
2013/01/24 Javascript
Jquery+CSS3实现一款简洁大气带滑动效果的弹出层
2013/05/15 Javascript
在NodeJS中启用ECMAScript 6小结(windos以及Linux)
2014/07/15 NodeJs
JavaScript错误处理
2015/02/03 Javascript
通过javascript进行UTF-8编码的实现方法
2016/06/27 Javascript
JavaScript数组去重的几种方法效率测试
2016/10/23 Javascript
懒加载实现的分页&amp;&amp;网站footer自适应
2016/12/21 Javascript
js实现九宫格拼图小游戏
2017/02/13 Javascript
JavaScript实现的原生态兼容IE6可调可控滚动文字功能详解
2017/09/19 Javascript
vue slots 组件的组合/分发实例
2018/09/06 Javascript
js如何获取图片url的Blob值并预览示例代码
2019/03/07 Javascript
微信小程序代码上传、审核发布小程序
2019/05/18 Javascript
微信小程序sessionid不一致问题解决
2019/08/30 Javascript
在vue中使用inheritAttrs实现组件的扩展性介绍
2020/12/07 Vue.js
Python中的rjust()方法使用详解
2015/05/19 Python
python 2.7.14安装图文教程
2018/04/08 Python
python实现微信定时每天和女友发送消息
2019/04/29 Python
python+openCV调用摄像头拍摄和处理图片的实现
2019/08/06 Python
python基于celery实现异步任务周期任务定时任务
2019/12/30 Python
已安装tensorflow-gpu,但keras无法使用GPU加速的解决
2020/02/07 Python
Python偏函数Partial function使用方法实例详解
2020/06/17 Python
python爬虫beautifulsoup解析html方法
2020/12/07 Python
Parfume Klik丹麦:香水网上商店
2018/07/10 全球购物
Rowdy Gentleman服装和配饰:美好时光
2019/09/24 全球购物
大专生自我鉴定怎么写
2014/09/16 职场文书
2014民事授权委托书范本
2014/09/29 职场文书
销售经理工作检讨书
2015/02/19 职场文书
2015年感恩母亲节的演讲稿
2015/03/18 职场文书
党员电教片《信仰》心得体会
2016/01/15 职场文书
2019年教师节祝福语精选,给老师送上真诚的祝福
2019/09/09 职场文书
nginx实现动静分离的方法示例
2021/11/07 Servers
错误码NET::ERR_CERT_DATE_INVALID证书已过期解决方法?
2022/07/07 数码科技