HTML5 File API — 让前端操作文件变的可能
前言
在 HTML5 File API 出现之前,前端对于文件的操作是非常有局限性的,大多需要配合后端实现。出于安全角度考虑,从本地上传文件时,代码不可能获取文件在用户本地的地址,所以纯前端不可能完成一些类似图片预览的功能。但是 File API 的出现,让这一切变成了可能。
跟着楼主由浅入深,了解下强大的 File API 吧。
FileList
FileList 对象针对表单的 file 控件。当用户通过 file 控件选取文件后,这个控件的 files 属性值就是 FileList 对象。它在结构上类似于数组,包含用户选取的多个文件。如果 file 控件没有设置 multiple 属性,那么用户只能选择一个文件,FileList 对象也就只有一个元素了。
<input type='file' multiple /><script> document.querySelector('input').onchange = function() { console.log(this.files); };</script>
比如我选择了两个文件,控制台打印:
FileList {0: File, 1: File, length: 2}0: File1: File length:2__proto__: Object
除了用 file 控件,采用拖放方式,也可以得到 FileList 对象。关于拖放,可以参考下我以前写的文章 HTML5 — 让拖放变的流行起来。
<textarea></textarea><script> var ipt = document.querySelector('textarea'); ipt.ondragover = function () { return false; }; // Add drop handler ipt.ondrop = function(e) { e.stopPropagation(); e.preventDefault(); e = e || window.event; var files = e.dataTransfer.files; console.log(files); };</script>
选中两个文件拖放到文本框中,打印结果和上面一致。
一般来说,我们不可能手动构造 FileList 对象,只能被动地读取,也就是说只有用户主动触发了文件读取行为,js 才能访问到 FileList,而这通常发生在表单选择文件或者拖拽文件中。
File
我们看到一个 FileList 对象包含了我们选中的 File 对象,那么一个 File 又有哪些属性呢?我们可以打印出来看看。
name:文件名,该属性只读。
size:文件大小,单位为字节,该属性只读。
type:文件的 MIME 类型,如果分辨不出类型,则为空字符串,该属性只读。
lastModified:文件的上次修改时间,格式为时间戳。
lastModifiedDate:文件的上次修改时间,格式为 Date 对象实例。
Blob
上图中我们看到,File 对象是继承自 Blob 对象的,Blob 又是什么鬼?
Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的 API(比如 File 对象),都是建立在 Blob 对象基础上的,继承了它的属性和方法。
生成 Blob 对象有两种方法:一种是使用 Blob 构造函数,另一种是对现有的 Blob 对象使用 slice 方法切出一部分。
(1)Blob 构造函数,接受两个参数。第一个参数是一个包含实际数据的数组,第二个参数是数据的类型,这两个参数都不是必需的。
var a = ["hello", "world"];var myBlob = new Blob(a, { "type" : "text/xml" });console.log(myBlob);
(2)Blob 对象的 slice 方法,将二进制数据按照字节分块,返回一个新的 Blob 对象。
var a = ["hello", "world"];var myBlob = new Blob(a, { "type" : "text/xml" });var newBlob = myBlob.slice(0, 5);console.log(newBlob);
Blob 对象有两个只读属性:
size:二进制数据的大小,单位为字节。(文件上传时可以在前端判断文件大小是否合适)
type:二进制数据的 MIME 类型,全部为小写,如果类型未知,则该值为空字符串。(文件上传时可以在前端判断文件类型是否合适)
FileReader
重头戏来了,FileReader API 才是我们接下去完成一些任务的关键。FileReader API 用于读取文件,即把文件内容读入内存。它的参数是 File 对象或 Blob 对象。
对于不同类型的文件,FileReader 提供不同的方法读取文件。
readAsBinaryString(Blob|File):返回二进制字符串,该字符串每个字节包含一个 0 到 255 之间的整数。(已废弃)
readAsText(Blob|File, opt_encoding):返回文本字符串。默认情况下,文本编码格式是 UTF-8,可以通过可选的格式参数,指定其他编码格式的文本。
readAsDataURL(Blob|File):返回一个基于 Base64 编码的 data-uri 对象。
readAsArrayBuffer(Blob|File):返回一个 ArrayBuffer 对象。
除了以上四种不同的读取文件方法,FileReader API 还有一个 abort 方法,用于中止文件上传。
var reader = new FileReader();reader.abort();
FileReader 对象采用异步方式读取文件,可以为一系列事件指定回调函数。
onabort 方法:读取中断或调用 reader.abort() 方法时触发。
onerror 方法:读取出错时触发。
onload 方法:读取成功后触发。
onloadend 方法:读取完成后触发,不管是否成功。触发顺序排在 onload 或 onerror 后面。
onloadstart 方法:读取将要开始时触发。
onprogress 方法:读取过程中周期性触发。(可以用来获取文件读取的进度)
以前在学习图片的 base64 编码的时候,写了一篇文章 获取图片 base64 编码的几种方法,当时还没有学习 File API,了解到 File API 也能做类似的事情,现在学到了,写了个简单的 demo http://hanzichi.github.io/2016/image2base64/,不仅能获取图片的 base64 编码,同时也能获取文字的 base64 编码,代码比较简单就不放了,可以 猛戳这里。
获取到了文件的 base64 编码,做一些诸如图片预览的功能,也就手到擒来了,有兴趣的可以自己尝试下,类似的还有文字预览啊,等等。
URL
你以为 File API 就这样了吗?非也,还有个强大的东西没有介绍,URL 对象!
调用 URL 对象的 createObjectURL 方法,传入一个 File 对象或者 Blob 对象,能生成一个链接,听起来好像很吊的样子。
var objecturl = window.URL.createObjectURL(blob);
上面的代码会对二进制数据生成一个 URL,这个 URL 可以放置于任何通常可以放置 URL 的地方,比如 img 标签的 src 属性。需要注意的是,即使是同样的二进制数据,每调用一次 URL.createObjectURL 方法,就会得到一个不一样的 URL。
这个 URL 的存在时间,等同于网页的存在时间,一旦网页刷新或卸载,这个 URL 就失效。(File 和 Blob 又何尝不是这样呢)除此之外,也可以手动调用 URL.revokeObjectURL 方法,使 URL 失效。
window.URL.revokeObjectURL(objectURL);
举个简单的例子。
var blob = new Blob(["Hello hanzichi"]);var a = document.createElement("a");a.href = window.URL.createObjectURL(blob);a.download = "a.txt";a.textContent = "Download";document.body.appendChild(a);
页面上生成了一个超链接,点击它就能下载一个名为 a.txt
的文件,里面的内容是 Hello hanzichi
。
这里插点题外话,简单介绍下 H5 新增的 download 属性。对于一些诸如 exe,rar 等浏览器不能直接打开的文件类型,我们一般可以直接用一个 a 标签,将其指向文件在服务端的地址,点击即可下载。但是如果是一些浏览器能直接打开的文件,比如 txt,js 等,如果这样设置一个超链接,点击会直接打开文件,一般我们可以配合后端实现,比如用 PHP。
$file_name = "1.txt"; // 下载文件名$file_dir = dirname(__FILE__). '/'; //下载文件存放目录//输入文件标签Header("Content-type: text/plain");Header("Content-Disposition: attachment; filename=" . $file_name );
以上代码需要文件的 Content-type 属性值,安利一个网址,http://tool.oschina.net/commons ,各种文件类型的 Content-type 属性值一网打尽!
如果考虑到安全性,header + fread 可能会显得更严谨。
$file_name = "1.txt"; // 下载文件名$file_dir = dirname(__FILE__). '/'; //下载文件存放目录Header("Content-type: text/plain");Header("Content-Disposition: attachment; filename=" . $file_name );echo fread($file, filesize($file_dir . $file_name));
但是现在我们只需要在 a 标签上加上 download !
<a href="1.txt" download>download txt></a>
还可以给 download 加上属性值,即为下载的文件名。
<a href="1.txt" download="2.txt">download txt></a>
可以省略 .txt
的后缀名,浏览器会自行判断。
我们再回到 URL 上来。对于 File 或者 Blob 对象,我们可以这样理解,它们的存在,依赖于页面,而 URL 能给这些 "转瞬即逝" 的二进制对象一个临时的指向地址。
这个临时的地址还有什么用呢?也能做图片预览,相比前面用 readAsDataURL 的实现,更简单了。
<input type='file' multiple /><br/><img /><script>document.querySelector("input").onchange = function() { var files = this.files; document.querySelector("img").src = window.URL.createObjectURL(files[0]);}</script>
比如还有这样的需求,前端上传文件,要动态生成该文件的下载链接,也能用 URL 完成。
Canvas & dataURL & Blob
canvas 中有 toDataURL 函数,可以将 canvas 转为 dataURL 形式的 base64 编码,而 Blob 也可以转为 dataURL,这三者之间是否可以互相转换?有没有什么实用之处?
(1)canvas -> dataURL
用 toDataURL 方法,比较简单,不多说。
(2)blob -> dataURL
用 FileReader 的 readAsDataURL 方法,使用方式可以看 这个 demo
(3)dataURL -> blob
这个函数有点屌
function dataURLtoBlob(dataurl) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--){ u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], {type:mime});}
(4)dataURL - canvas
将 image 的 src 属性置为 dataURL,再用 drawImage 方法画上去。
(5)blob - canvas
如何把二进制形式的图片画上 canvas?先用 readAsDataURL 转为 dataURL,接着就是 (4) 的事情了。
(6)canvas - blob
canvas 转为 blob 也可以用 dataURL 做跳板,先将 canvas 转为 dataURL(1),再用 dataURL 转为 blob(3)。
利用它们之间的转换可以做些什么好玩的事呢?比如可以上传图片,对图片做各种处理,然后保存,看起来好像挺好玩的,等有空了搞个 demo 出来。
2016.11.11 add: canvas 有原生的 toBlob 方法,使得图片文件可以被缓存或保存到本地。
Read more
File API 还能实现很多炫酷的功能,比如查看文件读取的进度(onprogress ),分割文件,分段读取文件,还能配合 ajax 使用,有兴趣的可以参考以下文献自行研究。
文件和二进制数据的操作(本文大量参考或者直接引用了阮老师的这篇文章内容,不得不说阮老师的 tutorial 写的真的非常的好,老少皆宜)
通过 File API 使用 JavaScript 读取文件
Using files from web applications
HTML5 读取 本地文件 File Api 使用
FileAPI w3
了解 HTML/HTML5 中的 download 属性
在浏览器端用 JS 创建和下载文件
DataURL 与 File,Blob,canvas 对象之间的互相转换的 Javascript