首页 > 技术文章 > 前端文件下载技术调研和实践

zhilin 2022-01-17 10:59 原文

download方式总结

form表单提交

这是以前常使用的传统方式,毕竟那个年代,没那么多好用的新特性呀。

道理也很简单,为一个下载按钮添加click事件,点击时动态生成一个表单,利用表单提交的功能来实现文件的下载(实际上表单的提交就是发送一个请求)

来看下如何生成一个表单,生成怎么样的一个表单:

/**
 * 下载文件
 * @param {String} path - 请求的地址
 * @param {String} fileName - 文件名
 */
function downloadFile (downloadUrl, fileName) {
    // 创建表单
    const formObj = document.createElement('form');
    formObj.action = downloadUrl;
    formObj.method = 'get';
    formObj.style.display = 'none';
    // 创建input,主要是起传参作用
    const formItem = document.createElement('input');
    formItem.value = fileName; // 传参的值
    formItem.name = 'fileName'; // 传参的字段名
    // 插入到网页中
    formObj.appendChild(formItem);
    document.body.appendChild(formObj);
    formObj.submit(); // 发送请求
    document.body.removeChild(formObj); // 发送完清除掉
}

 

优点

  • 传统方式,兼容性好,不会出现URL长度限制问题

缺点

  • 无法知道下载的进度
  • 无法直接下载浏览器可直接预览的文件类型(如txt/png等)

windows.open或location.href下载文件

最简单最直接的方式,实际上跟a标签访问下载链接一样

window.open('downloadFile.zip');
location.href = 'downloadFile.zip';

 

优点

  • 简单方便直接
  • 浏览器兼容性好

缺点

  • URL长度受限制
  • 需要注意url编码问题
  • 浏览器可直接浏览的文件类型是不提供下载的,如txt、png、jpg、gif等
  • 不能添加header,也就不能进行鉴权
  • 拿不到后端处理这个过程的时机,无法根据回调函数做交互以及进度提示

a标签的download

我们知道,a标签可以访问下载文件的地址,浏览器帮助进行下载。但是对于浏览器支持直接浏览的txt、png、jpg、gif等文件,是不提供直接下载(可右击从菜单里另存为)的。

为了解决这个直接浏览不下载的问题,可以利用download属性。

download属性是HTML5新增的属性,兼容性可以了解下 can i use download

总体兼容性算是很好了,基本可以区分为IE和其他浏览。但是需要注意一些信息:

  • Edge 13在尝试下载data url链接时会崩溃。
  • Chrome 65及以上版本只支持同源下载链接。
  • Firefox只支持同源下载链接。

基于上面描述,如果你尝试下载跨域链接,那么其实download的效果就会没了,跟不设置download表现一致。即浏览器能预览的还是会预览,而不是下载。

简单用法:

<a href="example.jpg" download>点击下载</a>

 

可以带上属性值,指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。

<a href="example.jpg" download="test">点击下载</a>

如上,会下载了一个名叫test的图片

监测是否支持download

要知道浏览器是否支持download属性,简单的一句代码即可区分

const isSupport = 'download' in document.createElement('a');

对于在跨域下不能下载可浏览的文件,其实可以跟后端协商好,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域就好了。

 

优点

  • 能解决不能直接下载浏览器可浏览的文件

缺点

  • 得已知下载文件地址
  • 不能下载跨域下的浏览器可浏览的文件
  • 有兼容性问题,特别是IE
  • 不能进行鉴权

利用Blob对象

该方法较上面的直接使用a标签download这种方法的优势在于,它除了能利用已知文件地址路径进行下载外,还能通过发送ajax请求api获取文件流进行下载。毕竟有些时候,后端不会直接提供一个下载地址给你直接访问,而是要调取api。

利用Blob对象可以将文件流转化成Blob二进制对象。该对象兼容性良好,需要注意的是

  • IE10以下不支持。
  • 在Safari浏览器上访问Blob UrlObject URL当前是有缺陷的,如下文中通过URL.createObjectURL生成的链接。caniuse官网有指出

Safari has a serious issue with blobs that are of the type application/octet-stream

进行下载的思路很简单:发请求获取二进制数据,转化为Blob对象,利用URL.createObjectUrl生成url地址,赋值在a标签的href属性上,结合download进行下载。

/**
 * 下载文件
 * @param {String} path - 下载地址/下载请求地址。
 * @param {String} name - 下载文件的名字/重命名(考虑到兼容性问题,最好加上后缀名)
 */
downloadFile (path, name) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', path);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        if (this.status === 200 || this.status === 304) {
            // 如果是IE10及以上,不支持download属性,采用msSaveOrOpenBlob方法,但是IE10以下也不支持msSaveOrOpenBlob
            if ('msSaveOrOpenBlob' in navigator) {
                navigator.msSaveOrOpenBlob(this.response, name);
                return;
            }
            // const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
            // const url = URL.createObjectURL(blob);
            const url = URL.createObjectURL(this.response);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
    };
}

该方法不能缺少a标签的download属性的设置。因为发请求时已设置返回数据类型为Blob类型(xhr.responseType = 'blob'),所以target.response就是一个Blob对象,打印出来会看到两个属性sizetype。虽然type属性已指定了文件的类型,但是为了稳妥起见,还是在download属性值里指定后缀名,如Firefox不指定下载下来的文件就会不识别类型。

大家可能会注意到,上述代码有两处注释,其实除了上述的写法外,还有另一个写法,改动一丢丢。如果发送请求时不设置xhr.responseType = 'blob',默认ajax请求会返回DOMString类型的数据,即字符串。这时就需要两处注释的代码了,对返回的文本转化为Blob对象,然后创建blob url,此时需要注释掉原本的const url = URL.createObjectURL(target.response)

 

优点

  • 能解决不能直接下载浏览器可浏览的文件
  • 可设置header,也就可添加鉴权信息

缺点

  • 兼容性问题,IE10以下不可用;Safari浏览器可以留意下使用情况

利用base64

这里的用法跟上面用Blob大同小异,基本上思路是一样的,唯一不同的是,上面是利用Blob对象生成Blob URL,而这里则是生成Data URL,所谓Data URL,就是base64编码后的url形式。

/**
 * 下载文件
 * @param {String} path - 下载地址/下载请求地址。
 * @param {String} name - 下载文件的名字(考虑到兼容性问题,最好加上后缀名)
 */
downloadFile (path, name) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', path);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        if (this.status === 200 || this.status === 304) {
            const fileReader = new FileReader();
            fileReader.readAsDataURL(this.response);
            fileReader.onload = function () {
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = this.result;
                a.download = name;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            };
        }
    };
}

 

优点

  • 能解决不能直接下载浏览器可浏览的文件
  • 可设置header,也就可添加鉴权信息

缺点

  • 兼容性问题,IE10以下不可用

Blob-对象介绍

Blob 是什么

一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在,允许我们可以通过JS直接操作二进制数据。

一个Blob对象就是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。

Blob对象可以看做是存放二进制数据的容器,此外还可以通过Blob设置二进制数据的MIME类型。

创建Blob

通过构造函数

var blob = new Blob(dataArr:Array<any>, opt:{type:string});

参数

  • dataArray:数组,一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。
  • opt:对象,用于设置Blob对象的属性(如:MIME类型)

 

使用字符串构造一个blob对象

const debug = {hello: "world"};
const blob = new Blob([JSON.stringify(debug)], {type : 'application/json'});

生成的blob对象如下:

Blob {
  size: 17
  type: "application/json"
  __proto__: Blob
}

Blob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为单位),type 是 MIME 类型的字符串。

通过Blob.slice()

const blob = new Blob(["javascript"], {type : 'text/plain'});
const blob2 = blob.slice(0,5,"text/plain");

生成的blob对象如下:

Blob {
  size: 5
  type: "text/plain"
  __proto__: Blob
}

通过canvas.toBlob()

const canvas = document.getElementById("canvas");
canvas.toBlob(function(blob){
	console.log(blob);
}

方法

slice()

Blob.slice() 方法用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象。

语法

var blob = instanceOfBlob.slice([start [, end [, contentType]]]};

参数

  • start 可选 表示被会被拷贝进新的 Blob 的字节的起始位置。如果传入的是一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值是0, 如果你传入的start的长度大于源 Blob 的长度,那么返回的将会是一个长度为0并且不包含任何数据的一个 Blob 对象。
  • end 可选 end-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。如果传入了一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值就是它的原始长度(size).
  • contentType 可选 给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。

stream()

返回一个ReadableStream对象,读取它将返回包含在Blob中的数据。

const stream = blob.stream();

text()

返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的 USVString。

Unicode 标量值( Unicode scalar values ):字符的代号。

在JavaScript中返回时, USVString 映射到 String 。它通常仅用于执行文本处理的 API,需要一串 unicode 标量值才能进行操作。

arrayBuffer()

返回一个 Promise 对象且包含 blob 所有内容的二进制格式的 ArrayBuffer。

FileReader.readAsArrayBuffer() 这个方法与之类似,但 arrayBuffer() 返回一个 promise 对象,而不是像 FileReader 一样返回一个基于事件的 API。

应用场景

分片上传

通过Blob.slice方法,可以将大文件分片,轮循向后台提交各文件片段,即可实现文件的分片上传。
分片上传逻辑如下:

  • 获取要上传文件的File对象,根据chunk(每片大小)对文件进行分片
  • 通过post方法轮循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息;post body中存放本次要上传的二进制数据片段
  • 接口每次返回offset,用于执行下次上传

下面是分片上传的简单实现:

initUpload();

//初始化上传
function initUpload() {
    var chunk = 100 * 1024;   //每片大小
    var input = document.getElementById("file");    //input file
    input.onchange = function (e) {
        var file = this.files[0];
        var query = {};
        var chunks = [];
        if (!!file) {
            var start = 0;
            //文件分片
            for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
                var end = start + chunk;
                chunks[i] = file.slice(start , end);
                start = end;
            }
            
            // 采用post方法上传文件
            // url query上拼接以下参数,用于记录上传偏移
            // post body中存放本次要上传的二进制数据
            query = {
                fileSize: file.size,
                dataSize: chunk,
                nextOffset: 0
            }

            upload(chunks, query, successPerUpload);
        }
    }
}

// 执行上传
function upload(chunks, query, cb) {
    var queryStr = Object.getOwnPropertyNames(query).map(key => {
        return key + "=" + query[key];
    }).join("&");
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://xxxx/opload?" + queryStr);
    xhr.overrideMimeType("application/octet-stream");
    
    //获取post body中二进制数据
    var index = Math.floor(query.nextOffset / query.dataSize);
    getFileBinary(chunks[index], function (binary) {
        if (xhr.sendAsBinary) {
            xhr.sendAsBinary(binary);
        } else {
            xhr.send(binary);
        }

    });

    xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                var resp = JSON.parse(xhr.responseText);
                // 接口返回nextoffset
                // resp = {
                //     isFinish:false,
                //     offset:100*1024
                // }
                if (typeof cb === "function") {
                    cb.call(this, resp, chunks, query)
                }
            }
        }
    }
}

// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
    if (resp.isFinish === true) {
        alert("上传成功");
    } else {
        //未上传完毕
        query.offset = resp.offset;
        upload(chunks, query, successPerUpload);
    }
}

// 获取文件二进制数据
function getFileBinary(file, cb) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function (e) {
        if (typeof cb === "function") {
            cb.call(this, this.result);
        }
    }
}

以上是文件分片上传前端的简单实现,当然,此功能还可以更加完善,如后台需要对合并后的文件大小进行校验;或者前端加密文件,全部上传完毕后后端解密校验等,此处不做赘述。

存储下载数据

从互联网上下载的数据可以存储到 Blob 对象中。 例如,在一些需要鉴权的图片接口中,我们可以使用fetch的方式,将鉴权信息附在请求里,下载得到blob对象,然后使用下面的方法,将blob作为url使用。或者在前端直接通过构建Blob对象进行前端文件下载。

axios.get('https://xxxxxx', {responseType: 'blob'})
.then(res => {
    let url = URL.createObjectURL(res.data)
    let a = document.createElement('a')
    a.setAttribute('download', '图片')
    a.href = url
    a.click()
})

Blob 用作 URL

Blob 可以很容易的作为 、 或其他标签的 URL。Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。

在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。相当于这个方法创建了一个传入对象的内存引用地址

其形式为 blob:/,在chrome中生成对应的示例如下:

"blob:chrome-extension://lecdifefmmfjnjjinhaennhdlmcaeeeb/0f36aeda-9351-4e38-9379-93efc9360f6b"

 

示例

将页面中的配置信息下载下来供用户方便使用。

const config = {
   name: 'lsqy',
   password: 'yourpassword',
   ak: 'XXXXXXXXXX',
   sk: 'XXXXXXXXXX'
}

// 生成blob对象
const blobContent = new Blob(
    [JSON.stringify(config, null, 2)],
    {type : 'application/json'}
);
// 构建下载链接
const blobUrl = window.URL.createObjectURL(blobContent)

const lnk = document.createElement('a')
link.download = filename
link.href = blobUrl
// 触发点击
eleLink.click()
当你结束使用某个 URL 对象之后,应该通过调用URL.revokeObjectURL()这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。

如果你拿到了一个blobURL,想要重新转成blob,只能将此url当作下载链接重新下载

axios.get('blob:XXX', {responseType: 'blob'})
.then(res => {
    // use res.data
})

Blob 转换为 Base64

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。

绝大多数现代浏览器都支持一种名为 Data URLs 的特性,允许使用 base64 对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。

Data URLs 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:

data:[<mediatype>][;base64],<data>

mediatype 是个 MIME 类型的字符串,例如 "image/jpeg" 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。

如果数据是文本类型,你可以直接将文本嵌入(根据文档类型,使用合适的实体字符或转义字符)。如果是二进制数据,你可以将数据进行 base64 编码之后再进行嵌入。比如嵌入一张图片:

<img alt="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">

在编写 HTML 网页时,对于一些简单图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求,如果你使用webpack打包的话,可以启用 file-loader 和 url-loader,配置小于某个大小的图片使用Data URL潜入到网页中。

使用base64还可以实现上传图片时的本地预览功能

readBlob(event) {
    const self = this;
    const reader = new FileReader();
    reader.onload = function () {
      const base64 = this.result;
   		// todo 这里将base64赋值给 img 标签的 src,用于本地预览
   		// img.src = base64
   		
     self.uploadImage(base64); // 上传base64数据到服务器
    };
    reader.readAsDataURL(event.target.files[0]);
  }

对于 FileReader 对象来说,除了支持把 Blob/File 对象转换为 Data URL 之外,它还提供了 readAsArrayBuffer() 和 readAsText() 方法,用于把 Blob/File 对象转换为其它的数据格式。

Blob 与 ArrayBuffer

  • Blob和ArrayBuffer都能存储二进制数据。Blob相对而言储存的二进制数据大(如File文件对象)。
  • ArrayBuffer对象表示原始的二进制数据缓冲区,即在内存中分配指定大小的二进制缓冲区(容器),用于存储各种类型化数组的数据,是最基础的原始数据容器,无法直接读取或写入, 需要通过具体视图来读取或写入,即TypedArray对象或DataView对象对内存大小进行读取或写入;Blob对象表示一个不可变、原始数据的类文件对象。
  • ArrayBuffer 是存在内存中的,可以直接操作。而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
  • 可以相互转换。 Blob => ArrayBuffer

 

let blob = new Blob([1,2,3,4])
let reader = new FileReader();
reader.onload = function(result) {
    console.log(result);
}
reader.readAsArrayBuffer(blob);
ArrayBuffer => Blob

let blob = new Blob([buffer])

blob与复制粘贴

粘贴 有时会遇到在输入框拦截图片进行上传的场景,这时候就是监听paste事件,并获取剪切板内的文件。

handlePaste (e) {
  if (this.paste) {
    this.uploadFiles(e.clipboardData.files);
  }
}

我们拿到的files就是基于blob的file类型。你可以使用FileReader的所有方法将blib变成你想要的样子。

复制 有时候我们需要点击按钮或右键菜单触发一个复制事件,将文本或图片扔进剪切板里。这时候我们也需要生成一个blob对象 如果是文本对象。

"text/html": new Blob(["<i>Markup</i> <b>text</b>. Paste me into a rich text editor."], { type: "text/html" }),
"text/plain": new Blob(["Fallback markup text. Paste me into a rich text editor."], { type: "text/plain" })

如果是图片等文件类型数据,就需要自己fetch请求下载图片为blob,然后扔到剪切板里。

new ClipboardItem({
   [blob.type]: blob
})

使用JSzip打包文件

简介

JSZip是一个用于创建,阅读和编辑.zip文件的JavaScript库,具有友好而简单的API。

安装

With npm : npm install jszip

With bower : bower install Stuk/jszip

如何使用

获取对象

在浏览器中

对于浏览器,直接引入:dist/jszip.js或者 dist/jszip.min.js

在nodejs中

var JSZip = require("jszip");

基本操作

创建JSZip实例:

var zip = new JSZip();
 

添加(或更新)文件和文件夹 :file(name, content).folder(name)

返回当前的JSZip实例,可以链接调用。

// create a file
zip.file("hello.txt", "Hello[p my)6cxsw2q");
// oops, cat on keyboard. Fixing !
zip.file("hello.txt", "Hello World\n");

// create a file and a folder
zip.file("nested/hello.txt", "Hello World\n");
// same as
zip.folder("nested").file("hello.txt", "Hello World\n");

使用.folder(name),返回的对象具有不同的根:如果在该对象上添加文件,则将它们放置在创建的子文件夹中。这只是一个视图,添加的文件也将在根对象中。

var photoZip = zip.folder("photos");
// this call will create photos/README
photoZip.file("README", "a folder with photos");

获取文件内容:
zip.file("hello.txt").async("string").then(function (data) {
  // data is "Hello World\n"
});

if (JSZip.support.uint8array) {
  zip.file("hello.txt").async("uint8array").then(function (data) {
    // data is Uint8Array { 0=72, 1=101, 2=108, more...}
  });
}

删除文件或文件夹.remove(name)

zip.remove("photos/README");
zip.remove("photos");
// same as
zip.remove("photos"); // by removing the folder, you also remove its content.

生成一个zip文件

使用.generateAsync(options).generateNodeStream(options)可以生成一个zip文件(不是真实文件,而是其在内存中的表示形式)

var promise = null;
if (JSZip.support.uint8array) {
  promise = zip.generateAsync({type : "uint8array"});
} else {
  promise = zip.generateAsync({type : "string"});
}

读取一个zip文件

使用.loadAsync(data)可以加载一个zip文件

var new_zip = new JSZip();
// more files !
new_zip.loadAsync(content)
.then(function(zip) {
    // you now have every files contained in the loaded zip
    zip.file("hello.txt").async("string"); // a promise of "Hello World\n"
});

FileSaver.js介绍

简介

FileSaver.js 在没有原生支持 saveAs() 的浏览器上实现了 saveAs() 接口。有一个 FileSaver.js 示例,演示如何保存各种媒体类型。

FileSaver.js 是在客户端保存文件的解决方案,非常适合需要生成文件,或者保存不应该发送到外部服务器的敏感信息的 web App。

FileSaver依赖于Blob,因为大部分brower对Blob的大小有限制,所以超大文本流的存储是不适用的。不过浏览器至少给Blob 500Mb的空间,对于一般的文本或图片流已经足够了。

如果有对存储巨大的需求,可以使用StreamSaver.js。然而StreamSaver并不是这么火,看stars就知道了。

FileSaver对canvas的支持,依赖于canvas-toBlob.js,在使用FileSaver的同时推荐结合canvas-toBlob.js使用。

支持的浏览器

支持特征检测:

try {
    var isFileSaverSupported = !!new Blob;
} catch (e) {}

安装

npm install file-saver --save
bower install file-saver

安装 Typscript 声明:

npm install @types/file-saver --save-dev

语法

import { saveAs } from 'file-saver';
FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })

如果不希望 FileSaver.js 自动提供 Unicode 文本编码提示(参见:字节顺序标记),请将 disableAutoBOM 参数设置为 true。

示例

使用 require 保存文本

var FileSaver = require('file-saver');
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

保存文本

var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

保存网址

FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

在相同来源内使用URL只会使用a[download]。否则,它将首先检查它是否支持带有同步头请求的cors头。如果是这样,它将下载数据并使用Blob URL保存。如果没有,它将尝试使用下载它a[download]

标准的W3C File APIBlob接口并非在所有浏览器中都可用。 Blob.jsBlob解决此问题的跨浏览器实现。

保存画布(canvas)

var canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
saveAs(blob, "pretty image.png");
});

注意:标准的 HTML5 canvas.toBlob() 方法不兼容所有浏览器。
canvas-toBlob.js 是一个跨浏览器的实现 canvas.toBlob() 的 polyfill 方案。

保存文件

你可以保存一个文件结构,不需要指定文件名。文件自身已经包含了文件名,有一些方法来获取文件实例(从 storage,file input,新的构造函数)
如果你想修改文件名,你可以在第二个参数设置文件名。

// Note: Ie and Edge don't support the new File constructor,
// so it's better to construct blobs and use saveAs(blob, filename)
var file = new File(["Hello, world!"], "hello world.txt", {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(file);

实践(使用JSzip和FileSaver批量下载文件)

线上demo:http://yuzhilin.fe.sensorsdata.cn

源码地址:http://gitlab.internal.sensorsdata.cn/yuzhilin/download-app

推荐阅读