在web
开发中,若是你想让用户下载或者导出一个文件,应该怎么作呢?
传统的作法是在后端存储或者即时生成一个文件来提供下载功能,这样的优点是能够作权限控制、数据二次处理,但缺点是须要额外发起请求、增大服务端压力、下载速度慢。javascript
但随着HTML5
的标准发布,咱们已经可以作到只前端来下载各类文件了。css
在常规的HTTP应答中,Content-Disposition
消息头指示回复的内容该以何种形式展现,是之内联的形式(即网页或者页面的一部分),仍是以附件的形式下载并保存到本地。在
HTTP
场景中,第一个参数或者是inline
(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展现),或者是attachment
(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将filename
的值预填为下载后的文件名)。html
咱们在后端响应头中只要设置该头部信息,便可下载为文件,而不是请求并展现:前端
Content-Type: text/html; charset=utf-8 Content-Disposition: attachment; filename="cool.html"
但须要注意的是,若是想要用这种方式下载文件,不能使用AJAX
的方式,而是应该新建一个<a>
标签,模拟点击下载。缘由为处于安全性考虑,JavaScript
没法与磁盘进行交互,所以AJAX获得的内容将被保留在内存中,而不是磁盘上。java
Nginx
添加header
头下载location ~ \.(jpg|jpeg|png|bmp|ico|gif|swf)$ { add_header Content-Disposition 'attachment; filename="cool.html"'; }
和后端同样的原理,只不过头部信息经过Nginx
统一添加。git
<a>
标签的download
属性此属性指示浏览器下载URL
而不是导航到它,所以将提示用户将其保存为本地文件。若是属性有一个值,那么它将做为下载的文件名使用。此属性对容许的值没有限制,可是/
和\
会被转换为下划线。
URLs
。HTTP URL
须要位于同一源中,可是可使用 blob: URLs
和 data: URLs
,以方便用户下载 JavaScript
方式生成的内容(例如使用在线绘图的Web
应用建立的照片)。常规的<a>
标签,用于连接的跳转,如新的页面,那么若是咱们给<a>
标签加上download
属性,就能很简单的让用户保存新的html
页面。github
<a download="PHP实现并发请求.html" href="https://segmentfault.com/a/1190000016343861">PHP实现并发请求</a>
首先咱们须要了解一个特殊的数据格式:Blob
。web
Blob
数据Blob(Binary Large Object,二进制类型的大对象)
,表示一个不可变的原始数据的类文件对象,咱们上传文件时经常使用的File
对象就继承于Blob
,并进行了扩展用于支持用户系统上的文件。json
咱们只能经过Blob()
构造函数来建立一个新的Blob
对象:segmentfault
Blob(blobParts[, options])
// 建立一个json类型的Blob对象,支持传入同类型数据的一个数组 var debug = {hello: "world"}; var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'}); // 此时blob的值 // Blob(22) {size: 22, type: 'application/json'}
Blob
对象存在两个只读属性:
size
: Blob 对象中所包含数据的大小(字节)。
type
: 一个字符串,代表该Blob对象所包含数据的MIME类型。若是类型未知,则该值为空字符串。
URL
对象和下载字符串文件URL
接口是一个用来建立 URLs
的对象,包含两个静态方法:
objectURL = URL.createObjectURL(blob)
建立一个URL(DOMString)
,包含一个惟一的blob连接(该连接协议为以blob:,后跟惟一标识浏览器中的对象的掩码)。这个 URL 的生命周期和建立它的窗口中的 document 绑定。
URL.revokeObjectURL(objectURL)
销毁以前使用URL.createObjectURL()方法建立的URL实例。浏览器会在文档退出的时候自动释放它们,可是为了得到最佳性能和内存使用情况,你应该在安全的时机主动释放掉它们。
var url = URL.createObjectURL(blob); // 此时url的值,跟document绑定,因此每一个页面建立的字符串均不一样 // blob:https://developer.mozilla.org/defe53c2-2882-43c6-b275-db2a57959789
此时,咱们在页面中建立一个新<a>
标签,点击便可下载咱们想要的文件:
<a href="blob:https://developer.mozilla.org/58702010-433d-4097-990f-e483d84cd02a" download="file.json">下载文件连接</a>
FileReader
读取Blob
数据想要读取Blob
数据的惟一方法是FileReader
。
FileReader
对象容许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File
或Blob
对象指定要读取的文件或数据。其中
File
对象能够是来自用户在一个<input>
元素上选择文件后返回的FileList
对象,也能够来自拖放操做生成的DataTransfer
对象,还能够是来自在一个HTMLCanvasElement
上执行mozGetAsFile()
方法后返回结果。
该对象包含3个属性:
FileReader.error
一个DOMException,表示在读取文件时发生的错误 。
FileReader.readyState
表示FileReader状态的数字。取值以下:常量名 值 描述 EMPTY 0 尚未加载任何数据. LOADING 1 数据正在被加载. DONE 2 已完成所有的读取请求.
FileReader.result
文件的内容。该属性仅在读取操做完成后才有效,数据的格式取决于使用哪一个方法来启动读取操做。
包含6个事件处理:onabort,onerror,onload,onloadstart,onloadend,onprogress
,这些再也不详细说明,由于 FileReader
继承自EventTarget
,因此全部这些事件也能够经过addEventListener
方法使用。
包含5个方法:
FileReader.abort()
停止读取操做。在返回时,readyState属性为DONE。
FileReader.readAsArrayBuffer()
开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.
FileReader.readAsBinaryString()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。
FileReader.readAsDataURL()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容。
FileReader.readAsText()
开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
所以咱们能够直接读取Blob
对象的数据:
var reader = new FileReader(); reader.addEventListener("loadend", function() { console.log(reader.result); }); reader.readAsDataURL(blob); // 此时result的值 // data:application/json;base64,ewogICJoZWxsbyI6ICJ3b3JsZCIKfQ== reader.readAsText(blob); // 此时result的值 // { // "hello": "world" // }
除了下载手动生成的字符串或对象,咱们还能提供下载图片的功能,一方面能用于支持Canvas
绘图的保存功能,一方面能提供批量下载图片等高级功能。
除了浏览器自带的右键保存,咱们还能够这么作来下载图片:
// 经过src获取图片的blob对象 function getImageBlob(url, cb) { var xhr = new XMLHttpRequest(); xhr.open("get", url, true); xhr.responseType = "blob"; xhr.onload = function() { if (this.status == 200) { cb(this.response); } }; xhr.send(); } let reader = new FileReader(); reader.addEventListener("loadend", function() { console.log(reader.result); }); getImageBlob('https://cdn.segmentfault.com/v-5c4ec07f/global/img/user-64.png', function(blob){ // 读取来看下下载的内容 reader.readAsDataURL(blob); // 最终生成的字符串 // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAA... // 生成下载用的URL对象 let url = URL.createObjectURL(blob); // 生成一个a标签,并模拟点击,便可下载,批量下载同理 let aDom = aDom = document.createElement('a'); aDom.href = url; aDom.download = 'download.json'; aDom.text = '下载文件'; document.getElementsByTagName('body')[0].appendChild(aDom); aDom.click(); });
excel
文件等若是你明白了下载的原理,那么全部的内容都可以理解,只不过是转换成对应的格式而已,固然,复杂格式的文档不须要你本身去配置,能够引入第三方库,在excel
文档方面我选择用 tableExport库:
// 引入CDN文件 'https://cdn.bootcss.com/xlsx/0.14.1/xlsx.core.min.js', 'https://cdn.bootcss.com/FileSaver.js/2014-11-29/FileSaver.min.js', 'https://cdn.bootcss.com/TableExport/5.2.0/js/tableexport.min.js' // 绑定下载事件,这个是我本身的场景下代码,可能不适合你们,具体的参考官方文档 const tableDom = $('#table'); $('.table-exportBtn', tableDom).on('click', function () { const tableExport = tableDom.tableExport({ formats: ['xlsx', 'txt'], filename: '表格下载', exportButtons: false }); const type = $(this).data().type; const exportData = tableExport.getExportData()[tableDom[0].id][type]; const {data, mimeType, filename, fileExtension, merges, RTL, sheetname} = exportData; // 源码里才能看到完整参数,官方文档没有写全,致使下载的文件格式错误 tableExport.export2file(data, mimeType, filename, fileExtension, merges, RTL, sheetname); });
默认的方法会自动生成下载按钮,但若是你想自定义下载功能,参考 exportButtons: false 设置 一节,但这个文档有问题,export2file
参数不完整,致使下载的xlsx
文件一直格式错误,经过查看源码,须要写全参数才能够,上面的示例里已经写出。
tableExport
库源码咱们能够看下tableExport
导出文件的核心代码,其导出为excel
格式比较复杂,由xlsx.core.min.js
来完成:
/** * Exports and downloads the file * @memberof TableExport.prototype * @param data {String} * @param mime {String} mime type * @param name {String} filename * @param extension {String} file extension * @param merges {Object[]} * @param RTL {Boolean} */ export2file: function(data, mime, name, extension, merges, RTL, sheetname) { var format = extension.slice(1); data = this.getRawData(data, extension, name, merges, RTL, sheetname); if (_isMobile && (format === _FORMAT.CSV || format === _FORMAT.TXT)) { // 拼凑指定格式的data:类型 URI var dataURI = "data:" + mime + ";" + this.charset + "," + data; this.downloadDataURI(dataURI, name, extension); } else { // TODO: error and fallback when `saveAs` not available saveAs(new Blob([data], { type: mime + ";" + this.charset }), name + extension, true); } }, // 先建立<a>标签,而后提供href和download属性,并模拟点击 downloadDataURI: function(dataURI, name, extension) { var encodedUri = encodeURI(dataURI); var link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", name + extension); document.body.appendChild(link); link.click(); },
xlsx
文件导出导出尚未仔细研究,感兴趣的能够查看其js-xlsx Github项目
上面咱们主要讲了下载背后的原理,你能够本身封装,也可使用现成的第三方库,如 download.js ,这个能提供大部分经常使用数据的下载;但若是你是要下载表格数据为excel格式,仍是推荐 tableExport.js 及其依赖组件。
@Oliveryoung
提供的其余解决方案