多年前,我在Goolge面试中被问及哪些改变会能使我得以提高Web的用户体验的。 在我的心目中的第一位是有不用<input type="file"> 控件实现文件操作的办法。尽管Web的多数部分已经改变,但操作文件还是最原始落后的方式。终于,HTML5新的API到来了,在较新的桌面浏览器中我们有更多操作文件的方式了 (iOS 暂时不支持)。

文件类型

文件类型定义在 File API[1] 的规范里,并且是File的抽象. 每个File实例都有以下属性:

  • name – 文件名
  • size – 文件的大小字节数
  • type – 文件的MIME type

一个File对象给出了一个文件的基本信息而无需直接访问文件内容。这点很重要,因为读取文件需要进行磁盘操作,文件的大小直接影响了读取过程消耗的总时间。一个File对象实是一个文件的引用,而获取文件的内容则是另一个完全不同的过程。

获得文件的引用

毫无疑问,访问用户的文件在Web中是严禁的因为这显然是一个安全问题。一个网页一打开它就扫描你的硬盘然后里面有啥全知道了那你肯定不愿意。必须经由用户授权你才能访问对方电脑里的文件。但并没有专门的授权对话框,因为用户可以在需要上传文件时随时授权给网页。

当你用<input type="file"> 控件时,你就同时授权了网页和服务器访问对应的文件。所以起初通过<input type="file"> 控件就可能得到File对象是合理的.

HTML5 定义了一个 files 属性给每个 <input type="file"> 控件. 是一个FileList集合, 就是一个类array的叫 FileList包含有所有被选中的文件对象(HTML5中文件控件都是可以多选的)。通过以下简单的代码即可以访问文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<input id="your-files"type="file"/>
<script type="text/javascript">// <![CDATA[
varcontrol = document.getElementById("your-files");
control.addEventListener("change",function(event) {
 
    // When the control has changed, there are new files
 
    vari = 0,
        files = control.files,
        len = files.length;
 
    for(; i < len; i++) {
        console.log("Filename: "+ files[i].name);
        console.log("Type: "+ files[i].type);
        console.log("Size: "+ files[i].size + " bytes");
    }
 
},false);
// ]]></script>


这几行简单的代码监听控件的 change 事件。先完文件即触发事件,然后循环输出全部 File 对象的信息。当然文件的属性是随时都可被javascript读的,你不必等到 change 触发才读.

拖放文件

访问表单里的文件还是需要有表单控件用户还要用浏览的方式找到对应的文件。万幸的是, HTML5 Drag and Drop[2] 让用户多了另一个选择文件的方式:简单的将文件从电脑里拖进浏览器即可。 你只 要利用以下两个事件即可。

为了能操作拖到指定区域里的文件,你必须监听dragover 和drop事件同时注销掉两者的默认行为。这么做是告诉浏览器由你来接管处理事件而不再执行默认行为如打开图片之类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div id="your-files"></div>
<script>
vartarget = document.getElementById("your-files");
 
target.addEventListener("dragover",function(event) {
    event.preventDefault();
},false);
 
target.addEventListener("drop",function(event) {
 
    // cancel default actions
    event.preventDefault();
 
    vari = 0,
        files = event.dataTransfer.files,
        len = files.length;
 
    for(; i < len; i++) {
        console.log("Filename: "+ files[i].name);
        console.log("Type: "+ files[i].type);
        console.log("Size: "+ files[i].size + " bytes");
    }
 
},false);
</script>

其中的event.dataTransfer.files是另一个可以获取文件信息的 FileList 对象。这些代码的作用跟form控件中操作fiel对象是一样的。

Ajax异步上传

有了文件的引用,你可以做更酷的事件了:通过Ajax上传文件。这通过 XMLHttpRequest Level 2[3]规范里制定的FormData 对象是完全可行的。此对象 好比form表单允许你通过append()方法以键值对的方式提交到服务器:

1
2
varform = newFormData();
form.append("name","Nicholas");

FormData 对象最有用的是可以直接追加文件进去,有效果的模拟了通过Form表单上传文件。你仅需给文件的引用拟个名剩下的浏览器帮你搞定:

1
2
3
4
5
6
7
8
9
10
11
12
// create a form with a couple of values
varform = newFormData();
form.append("name","Nicholas");
form.append("photo", control.files[0]);
 
// send via XHR - look ma, no headers being set!
varxhr = newXMLHttpRequest();
xhr.onload =function() {
    console.log("Upload complete.");
};
xhr.open("post","/entrypoint",true);
xhr.send(form);

完成将 FormData 对象通过send()方法传递后,对应的HTTP 头信息会自动帮你设置好。也无需再为表单的编码纠结了。提交表单后服务器要以从“photo”参数中获取到文件内容和从 “name”中获取对应的文件名称.。这样你可随意在后台执行其他逻辑了,而且能轻易的与传统的表单或AJAX处理相结合。

以以提到的已经在各大浏览器最新发布的版本中得到全面的支持,包括IE 10.

下期预告

你现在已经知道了在浏览器中访问文件信息的两个方法:文件上传控件和拖放。将来可能会有其他方法,现下这两种是你应该知道的。当然,读取文件的信息只是刚刚开始,下期我们将讲 读取文件内容的操作。

参考资料

  1. File API specification (editor’s draft)
  2. HTML5 Drag and Drop
  3. XMLHttpRequest Level 2

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox PublishingO’Reilly Publishing, or anyone else. I speak

原文