前端文件下载技术调研和实践
download方式总结
form表单提交
这是以前常使用的传统方式,毕竟那个年代,没那么多好用的新特性呀。
道理也很简单,为一个下载按钮添加click
事件,点击时动态生成一个表单,利用表单提交的功能来实现文件的下载(实际上表单的提交就是发送一个请求)
来看下如何生成一个表单,生成怎么样的一个表单:
|
优点
- 传统方式,兼容性好,不会出现URL长度限制问题
缺点
- 无法知道下载的进度
- 无法直接下载浏览器可直接预览的文件类型(如txt/png等)
windows.open或location.href下载文件
最简单最直接的方式,实际上跟a
标签访问下载链接一样
|
优点
- 简单方便直接
- 浏览器兼容性好
缺点
- 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
表现一致。即浏览器能预览的还是会预览,而不是下载。
简单用法:
|
可以带上属性值,指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。
|
如上,会下载了一个名叫test
的图片
监测是否支持download
要知道浏览器是否支持download
属性,简单的一句代码即可区分
|
对于在跨域下不能下载可浏览的文件,其实可以跟后端协商好,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域就好了。
优点
- 能解决不能直接下载浏览器可浏览的文件
缺点
- 得已知下载文件地址
- 不能下载跨域下的浏览器可浏览的文件
- 有兼容性问题,特别是IE
- 不能进行鉴权
利用Blob对象
该方法较上面的直接使用a
标签download
这种方法的优势在于,它除了能利用已知文件地址路径进行下载外,还能通过发送ajax请求api获取文件流进行下载。毕竟有些时候,后端不会直接提供一个下载地址给你直接访问,而是要调取api。
利用Blob
对象可以将文件流转化成Blob
二进制对象。该对象兼容性良好,需要注意的是
- IE10以下不支持。
- 在Safari浏览器上访问
Blob Url
或Object 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
进行下载。
|
该方法不能缺少a
标签的download
属性的设置。因为发请求时已设置返回数据类型为Blob
类型(xhr.responseType = 'blob'
),所以target.response
就是一个Blob
对象,打印出来会看到两个属性size
和type
。虽然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形式。
|
优点
- 能解决不能直接下载浏览器可浏览的文件
- 可设置header,也就可添加鉴权信息
缺点
- 兼容性问题,IE10以下不可用
Blob-对象介绍
Blob 是什么
一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在,允许我们可以通过JS直接操作二进制数据。
一个Blob对象就是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。
Blob对象可以看做是存放二进制数据的容器,此外还可以通过Blob设置二进制数据的MIME类型。
创建Blob
通过构造函数
|
参数
- dataArray:数组,一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。
- opt:对象,用于设置Blob对象的属性(如:MIME类型)
使用字符串构造一个blob对象
|
生成的blob对象如下:
|
Blob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为单位),type 是 MIME 类型的字符串。
通过Blob.slice()
|
生成的blob对象如下:
|
通过canvas.toBlob()
|
方法
slice()
Blob.slice() 方法用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象。
语法
|
参数
- start 可选 表示被会被拷贝进新的 Blob 的字节的起始位置。如果传入的是一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值是0, 如果你传入的start的长度大于源 Blob 的长度,那么返回的将会是一个长度为0并且不包含任何数据的一个 Blob 对象。
- end 可选 end-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。如果传入了一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值就是它的原始长度(size).
- contentType 可选 给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
stream()
返回一个ReadableStream对象,读取它将返回包含在Blob中的数据。
|
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,用于执行下次上传
下面是分片上传的简单实现:
|
以上是文件分片上传前端的简单实现,当然,此功能还可以更加完善,如后台需要对合并后的文件大小进行校验;或者前端加密文件,全部上传完毕后后端解密校验等,此处不做赘述。
存储下载数据
从互联网上下载的数据可以存储到 Blob 对象中。 例如,在一些需要鉴权的图片接口中,我们可以使用fetch的方式,将鉴权信息附在请求里,下载得到blob对象,然后使用下面的方法,将blob作为url使用。或者在前端直接通过构建Blob对象进行前端文件下载。
|
Blob 用作 URL
Blob 可以很容易的作为 、 或其他标签的 URL。Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。
在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。相当于这个方法创建了一个传入对象的内存引用地址
其形式为 blob:/,在chrome中生成对应的示例如下:
|
示例
将页面中的配置信息下载下来供用户方便使用。
|
当你结束使用某个 URL 对象之后,应该通过调用URL.revokeObjectURL()这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。
如果你拿到了一个blobURL,想要重新转成blob,只能将此url当作下载链接重新下载
|
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 编码之后再进行嵌入。比如嵌入一张图片:
|
在编写 HTML 网页时,对于一些简单图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求,如果你使用webpack打包的话,可以启用 file-loader 和 url-loader,配置小于某个大小的图片使用Data URL潜入到网页中。
使用base64还可以实现上传图片时的本地预览功能
|
对于 FileReader 对象来说,除了支持把 Blob/File 对象转换为 Data URL 之外,它还提供了 readAsArrayBuffer() 和 readAsText() 方法,用于把 Blob/File 对象转换为其它的数据格式。
Blob 与 ArrayBuffer
- Blob和ArrayBuffer都能存储二进制数据。Blob相对而言储存的二进制数据大(如File文件对象)。
- ArrayBuffer对象表示原始的二进制数据缓冲区,即在内存中分配指定大小的二进制缓冲区(容器),用于存储各种类型化数组的数据,是最基础的原始数据容器,无法直接读取或写入, 需要通过具体视图来读取或写入,即TypedArray对象或DataView对象对内存大小进行读取或写入;Blob对象表示一个不可变、原始数据的类文件对象。
- ArrayBuffer 是存在内存中的,可以直接操作。而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
- 可以相互转换。 Blob => ArrayBuffer
|
blob与复制粘贴
粘贴 有时会遇到在输入框拦截图片进行上传的场景,这时候就是监听paste事件,并获取剪切板内的文件。
|
我们拿到的files就是基于blob的file类型。你可以使用FileReader的所有方法将blib变成你想要的样子。
复制 有时候我们需要点击按钮或右键菜单触发一个复制事件,将文本或图片扔进剪切板里。这时候我们也需要生成一个blob对象 如果是文本对象。
|
如果是图片等文件类型数据,就需要自己fetch请求下载图片为blob,然后扔到剪切板里。
|
使用JSzip打包文件
简介
JSZip是一个用于创建,阅读和编辑.zip文件的JavaScript库,具有友好而简单的API。
安装
|
如何使用
获取对象
在浏览器中
对于浏览器,直接引入:dist/jszip.js或者
dist/jszip.min.js
。
在nodejs中
|
基本操作
创建JSZip实例:
|
添加(或更新)文件和文件夹 :file(name, content)
和.folder(name)
。
返回当前的JSZip实例,可以链接调用。
|
使用.folder(name)
,返回的对象具有不同的根:如果在该对象上添加文件,则将它们放置在创建的子文件夹中。这只是一个视图,添加的文件也将在根对象中。
|
获取文件内容:
|
删除文件或文件夹.remove(name)
:
|
生成一个zip文件
使用.generateAsync(options)
或.generateNodeStream(options)
可以生成一个zip文件(不是真实文件,而是其在内存中的表示形式)
|
读取一个zip文件
使用.loadAsync(data)可以加载一个zip文件
|
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使用。
支持的浏览器
支持特征检测:
|
安装
|
安装 Typscript 声明:
|
语法
|
如果不希望 FileSaver.js 自动提供 Unicode 文本编码提示(参见:字节顺序标记),请将 disableAutoBOM 参数设置为 true。
示例
使用 require 保存文本
|
保存文本
|
保存网址
|
在相同来源内使用URL只会使用a[download]
。否则,它将首先检查它是否支持带有同步头请求的cors头。如果是这样,它将下载数据并使用Blob URL保存。如果没有,它将尝试使用下载它a[download]
。
标准的W3C File APIBlob
接口并非在所有浏览器中都可用。 Blob.js是Blob
解决此问题的跨浏览器实现。
保存画布(canvas)
|
注意:标准的 HTML5 canvas.toBlob()
方法不兼容所有浏览器。
canvas-toBlob.js 是一个跨浏览器的实现 canvas.toBlob()
的 polyfill 方案。
保存文件
你可以保存一个文件结构,不需要指定文件名。文件自身已经包含了文件名,有一些方法来获取文件实例(从 storage,file input,新的构造函数)
如果你想修改文件名,你可以在第二个参数设置文件名。
|
实践(使用JSzip和FileSaver批量下载文件)
线上demo:http://yuzhilin.fe.sensorsdata.cn
源码地址:http://gitlab.internal.sensorsdata.cn/yuzhilin/download-app