HTML5 引入了一个 File API 用以提供用户上传文件的信息,并允许网页中的 JavaScript 访问其内容。

以下是一些表单 file 控件:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">
<input type="file" accept="image/*;capture=camera">直接调用相机(测试安卓可以,iphone还是有相册)
<input type="file" accept="image/*" />调用相机 图片或者相册
<input type="file" multiple accept="image/*" />调用相册

1 FileList 对象

FileList 对象针对表单的 file 控件。 当用户通过 file 控件选取文件后,这个控件的 files 属性值就是 FileList 对象。

// 多选控件

<input type='file' multiple />
<script>
document.querySelector('input').onchange = function() {
console.log(this.files);

};
</script>

控制台输出:

FileList {0: File, 1: File, length: 2}
0: File
1: File
length:2
__proto__: Object

除了用 file 控件,采用拖放方式,也可以得到 FileList 对象。

<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,而这通常发生在表单选择文件或者拖拽文件中。

2 File 对象

我们看到一个 FileList 对象包含了我们选中的 File 对象,那么一个 File 又有哪些属性呢?我们可以打印出来看看。

HTML5 File API_xml

  • name:文件名,该属性只读。
  • size:文件大小,单位为字节,该属性只读。
  • type:文件的 MIME 类型,如果分辨不出类型,则为空字符串,该属性只读。
  • lastModified:文件的上次修改时间,格式为时间戳。
  • lastModifiedDate:文件的上次修改时间,格式为 Date 对象实例。

3 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 类型,全部为小写,如果类型未知,则该值为空字符串。(文件上传时可以在前端判断文件类型是否合适)

 

4FileReader

重头戏来了,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 编码。

<title>获取图片 base64 编码</title>
<meta charset="utf-8">
<style>
textarea {
width: 100%;
height: 200px;
margin-top: 10px;
}
</style>
<body>
<h1>上传图片或者拖动图片到框内获取图片 base64 编码</h1>
<input type='file' multiple /><br/>
<textarea></textarea>
<script>
document.querySelector('input').onchange = function() {
var reader = new FileReader();
reader.onload = function() {
var dataURL = reader.result;
document.querySelector('textarea').innerHTML = dataURL;
};
reader.readAsDataURL(this.files[0]);
};
// drag & drop
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;
var reader = new FileReader();
reader.onload = function() {
var dataURL = reader.result;
document.querySelector('textarea').innerHTML = dataURL;
};
reader.readAsDataURL(files[0]);
};
</script>
</body>

 

获取到了文件的 base64 编码,做一些诸如图片预览的功能,也就手到擒来了,有兴趣的可以自己尝试下,

类似的还有文字预览啊,等等。

5 URL

你以为 File API 就这样了吗?非也,还有个强大的东西没有介绍,URL 对象!

调用 URL 对象的 createObjectURL 方法,传入一个 File 对象或者 Blob 对象,能生成一个链接,听起来好像很吊的样子。

var

上面的代码会对二进制数据生成一个 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 属性。

是否支持download属性的监测
要监测当前浏览器是否支持​​​download​​属性,一行JS代码就可以了,如下:

var isSupportDownload = 'download' in document.createElement('a');

对于一些诸如 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 属性值,安利一个网址,各种文件类型的 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 完成。

6 onprogress 事件

想要上传图片的时候显示进度条,就要使用上 HTML5 中的 onprogress 事件了。
onprogress 事件可以作用于 <video> 或 <audio> 标签中,作为其状态回调函数的作用。
也可以是作为 XMLHttpRequest 的指定事件,XMLHttpRequest 对象,传送数据的时候,有一个 progress 事件,用来返回进度信息。
它分成 上传和下载 两种情况:

下载的 progress 事件属于 XMLHttpRequest 对象
上传的 progress 事件属于 XMLHttpRequest.upload 对象
当上传或下载时,会频繁调用该方法。该方法接受一个事件参数 event,其中 event.total 是需要传输的总字节,event.loaded 是已经传输的字节。如果event.lengthComputable 不为真,则 event.total 等于 0。

例子代码:

function uploadFile1(file,desc,catid){
var $process = $("#process_"+file.size);
var formData = new FormData(); //以下为像后台提交图片数据
formData .append('img_url', file);
formData .append('img_desc',desc);
formData .append('cat_id',catid);
var xhr=new XMLHttpRequest();
xhr.open('post','shop_gallery.php?act=update',true);
xhr.onreadystatechange=function (){
if(this.readyState==4){
$process.css("width","0%");
if (!(img_url.length == 0)) {
img_desc.splice(0, 1);
uploadFile1(img_url[0],img_desc[0],catid);
}
}
}
xhr.upload.onprogress=function (ev){
if(ev.lengthComputable){
var precent=100 * ev.loaded/ev.total;
$process.css("width",precent+'%');
if (precent == 100){
delimg1($process);
if (img_url.length == 0){
setTimeout("refresh("+catid+")", 1000 );
}
}
}
}
//
xhr.send(formData);
}

7 Canvas & dataURL & Blob

canvas 中有 toDataURL 函数,可以将 canvas 转为 dataURL 形式的 base64 编码,而 Blob 也可以转为 dataURL,这三者之间是否可以互相转换?有没有什么实用之处?

(1)canvas -> dataURL

用 toDataURL 方法,比较简单,不多说。

(2)blob -> dataURL

用 FileReader 的 readAsDataURL 方法,使用方式可以看​

(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)。

利用它们之间的转换可以做些什么好玩的事呢?比如可以上传图片,对图片做各种处理,然后保存。

canvas 有原生的 ​​toBlob​​ 方法,使得图片文件可以被缓存或保存到本地。

Read more

File API 还能实现很多炫酷的功能,比如查看文件读取的进度(onprogress ),分割文件,分段读取文件,还能配合 ajax 使用,有兴趣的可以参考以下文献自行研究。