我们知道,页面上展示的文件(图片)常见的有:文件的链接、base64格式的图片。文件链接是一个地址,base64格式的是一个字符串描述的文件,我们可以直接打开,或者浏览器里面直接右击下载。假设我们有需求是用户不想或者不知道浏览器可以直接下载图片,他希望出来一个按钮,点击按钮即可下载,那么我们该如何处理?
下载有链接的图片
假设有文件的地址,我们可以直接使用创建连接,然后模拟点击的操作来下载:
1 |
|
那么问题来了,为什么a标签的点击可以直接下载呢?
HTML <a>
元素可以通过它的href属性创建通向其他网页、文件、同一页面内的位置、电子邮件地址或其他任何URL的超链接。如果存在href
属性,当<a>
元素聚焦时按下回车键就可以激活它(或者直接点击)。
<a>
标签除了常用的href
属性外,还有download
属性,这个属性指示浏览器下载url而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么此值将在下载过程中作为预填充的文件名(如果用户需要,仍然可以更改文件名)。
需要注意的是,download
属性只适用于同源URL。如果不同源,仍然会导航到该地址。
下载base64格式的图片
我们有些图片不是直接从服务器请求地址,而是一串base64格式的字符,那么我们如何下载这种格式的图片?
首先,我们的base64的图片是一个描述图片的字符序列,而我们要下载的是图片,是文件,那么数据文本如何转换为文件?我们需要借助Blob
。
Blob
表示一个不可变的、原始数据的类文件对象,File
接口基于Blob
。我们可以使用window.URL.createObjectURL(blob)
来创建图片的链接,然后接下来的操作就是创建<a>
标签,模拟点击下载即可。
那关键的步骤就是如何将base64的数据转换为Blob
。
我们base64格式的数据是一个Data URLs
数据,即前缀为data:
协议的URL,其允许内容创建者向文档中嵌入小文件。
Data URLs
由四个部分组成:
- 前缀(
data:
) - 指示数据类型的MIME类型
- 如果非文本则可选的
base64
标记 - 数据本身
1 | data:[<mediatype>][;base64],<data> |
mediatype
是个MIME类型的字符串,如image/jpeg
表示JPEG图像的文件。如果被省略,则默认为text/plain;charset=US-ASCII
。
我们来查看一个base64描述的二维码:
1 | data:image/png;base64,iVBORw0KGgoAAAANS.....yBpoptXl+gmwNKw/sDrSJjDtDFcosAAAAASUVORK5CYII= |
我们再看下base64
数据是啥。
base64
是一组类似于二进制文本的编码规则,使得二进制数据在解释成radix-64
的表现形式后能够用ASCII字符串格式表示出来。base64编码普遍用于需要通过被设计为处理文本数据的媒介上存储和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。base64也被一些应用(包括使用MIME的垫资邮件)和在XML中存储复杂数据时使用。
我们可以通过atob()
函数解码通过base64
编码的字符串数据,通过btoa()
函数能够从二进制数据创建一个base64
编码的字符串.
ps:a 代表 ASCII码,b 代表二进制。
我们对上面的base64格式的图像进行解码:
可以看到解码后的数据为二进制数据。每一个base64字符实际上代表着6比特位,因此,3字节(一字节是8比特,3字节就是24比特)的字符串(二进制文件)可以转换为4个base64字符(4x6=24bit)。这意味着转换为base64格式的字符串相比原始尺寸增加了大约33%。如果编码的数据很少,可能增加的体积就越大。
有意思的是,如果bit数不能被4整除,需要在末尾加1或2个byte,并且末尾的0不能使用A而使用=。这就是为什么base64有的编码后会有一两个等号的缘由。
那么解码后的二进制字符串如何和blob结合?
我们可以看下Blob
的构造函数:
1 | var aBlob = new Blob(array, options) |
参数:
- array 是一个由
ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
等对象构成的Array
,或者其他类对象混合体。DOMString会被编码为UTF-8. - options 是一个可选的
BlobPropertyBag
字典,主要需要type
,它代表被放入Blob中的数组内容的MIME类型。
我们知道传入的应该是一个二进制数据缓冲区。我们无法直接操作ArrayBuffer
,而是通过类型数组对象或者DataView
对象来操作,它们会将缓冲区的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
JavaScript类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。
类型化数组是一种类似数组的对象,并提供一种用于访问原始二进制数据的机制。和Array
相比,类型数组针对于处理音频、视频、文件的操作,很明显会更好。
为达到最大的灵活性和效率,JavaScript类型数组将实现拆分为缓冲和视图两部分。一个缓冲(由ArrayBuffer
实现)描述的是一个数据块,缓冲没有格式可言,并且不提供机制访问其内容。为了访问和操作缓冲,我们需要使用视图。视图提供了上下文–即数据类型、起始偏移量和元素数——将数据转换为实际有类型的数组。类型数组架构图:
我们可以借助Unit8Array
类型数组。Unit8Array
数组类型表示一个8位无符号型数组,创建时内容被初始化为0,创建完后,可以以对象的方式或以数组方式引用数组中的元素。
综合上面的思路,我们可以实现一个转换方法:
1 | // 从dataURI中获得图片的类型 |
这样我们就可以将base64格式描述的图片转换为Blob对象。但是我们创建的对象只是存在于内存中的类文件对象,没法在放在页面上。我们需要通过URL.createObjectURL()
方法来将文件和DOM进行关联。
URL.createObjectURL()
静态方法会创建一个DOMString
。它包含一个表示参数中给的对象的URL。这个URL的生命周期和创建它的窗口中的Document
绑定。这个新的URL对象表示指定的File
对象或Blob
对象。
需要注意的是,使用URL.createObjectURL()
方法都会创建一个新的URL对象,即使已经用相同的对象参数创建过。所以当不需要这个URL对象时,需要调用URL.revokeObjectURL()
方法来释放。注意,释放的时候传递给revokeObjectURL
方法的参数应当是创建出来的URL对象DOMString
。
那么我们的下载方法可以有:
1 | function downLoadFile(fileName, uri) { |
最终代码:
1 | function getType (dataURI) { |
经过漫长的搜索和查对应的方法,终于搞清楚了blob和ArrayBuffer之间的关系,以及如何转换。受益匪浅啊!