zoukankan      html  css  js  c++  java
  • 前端js实现字符串/图片/excel文件下载

    web开发中,如果你想让用户下载或者导出一个文件,应该怎么做呢?
    传统的做法是在后端存储或者即时生成一个文件来提供下载功能,这样的优势是可以做权限控制、数据二次处理,但缺点是需要额外发起请求、增大服务端压力、下载速度慢。

    但随着HTML5的标准发布,我们已经能够做到只前端来下载各种文件了。

    后端响应式下载

    在常规的HTTP应答中,Content-Disposition 消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。

    HTTP场景中,第一个参数或者是inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将filename的值预填为下载后的文件名)。

    我们在后端响应头中只要设置该头部信息,即可下载为文件,而不是请求并展示:

    
    Content-Type: text/html; charset=utf-8
    Content-Disposition: attachment; filename="cool.html"
    

    但需要注意的是,如果想要用这种方式下载文件,不能使用AJAX的方式,而是应该新建一个<a>标签,模拟点击下载。原因为处于安全性考虑,JavaScript无法与磁盘进行交互,因此AJAX得到的内容将被保留在内存中,而不是磁盘上。

    前端下载:<a>标签的download属性

    此属性指示浏览器下载URL而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么它将作为下载的文件名使用。此属性对允许的值没有限制,但是/会被转换为下划线。
    1. 此属性仅适用于同源 URLs
    2. 尽管HTTP URL需要位于同一源中,但是可以使用 blob: URLsdata: URLs ,以方便用户下载 JavaScript 方式生成的内容(例如使用在线绘图的Web应用创建的照片)。

    常规的<a>标签,用于链接的跳转,如新的页面,那么如果我们给<a>标签加上download属性,就能很简单的让用户保存新的html页面。

    
    &lt;a download="PHP实现并发请求.html" href="https://segmentfault.com/a/1190000016343861"&gt;PHP实现并发请求&lt;/a&gt;
    

    生成并下载字符串文件

    首先我们需要了解一个特殊的数据格式:Blob

    Blob数据

    Blob(Binary Large Object,二进制类型的大对象),表示一个不可变的原始数据的类文件对象,我们上传文件时常用的File对象就继承于Blob,并进行了扩展用于支持用户系统上的文件。

    我们只能通过Blob()构造函数来创建一个新的Blob对象:

    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>标签,点击即可下载我们想要的文件:

    
    &lt;a href="blob:https://developer.mozilla.org/58702010-433d-4097-990f-e483d84cd02a" download="file.json"&gt;下载文件链接&lt;/a&gt;
    

    FileReader读取Blob数据

    想要读取Blob数据的唯一方法是FileReader

    FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 FileBlob 对象指定要读取的文件或数据。

    其中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 &amp;&amp; (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);
          }
        },
        // 先创建&lt;a&gt;标签,然后提供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 及其依赖组件。

    参考资料

    1. MDN-a: https://developer.mozilla.org...
    2. MDN-blob: https://developer.mozilla.org...
    3. 掘金-细说Web API中的Blob:https://juejin.im/post/59e35d...
    4. MDN-URL: https://developer.mozilla.org...
    5. MDN-FileReader: https://developer.mozilla.org...
    6. 博客园-js 获取图片url的Blob值并预览:https://www.cnblogs.com/tujia...
    7. tableExport文档:https://tableexport.v5.travis...
    8. 感谢 @Oliveryoung 提供的其他解决方案
    9. MDN-Content-Disposition: https://developer.mozilla.org...
    10. Ajax请求无法下载文件的原因: https://blog.csdn.net/w405722...
    11. Github-download.js: https://github.com/rndme/down...

    来源:https://segmentfault.com/a/1190000018143902

  • 相关阅读:
    Leetcode Excel Sheet Column Number
    AlgorithmsI PA2: Randomized Queues and Deques Subset
    AlgorithmsI PA2: Randomized Queues and Deques RandomizedQueue
    AlgorithmsI PA2: Randomized Queues and Deques Deque
    AlgorithmsI Programming Assignment 1: PercolationStats.java
    hdu多校第四场 1003 (hdu6616) Divide the Stones 机智题
    hdu多校第四场 1007 (hdu6620) Just an Old Puzzle 逆序对
    hdu多校第四场1001 (hdu6614) AND Minimum Spanning Tree 签到
    hdu多校第三场 1007 (hdu6609) Find the answer 线段树
    hdu多校第三场 1006 (hdu6608) Fansblog Miller-Rabin素性检测
  • 原文地址:https://www.cnblogs.com/qixidi/p/10390755.html
Copyright © 2011-2022 走看看