要实现文件上传,要兼顾IE6份额还不小的市场情况,除了fashion的HTML5,SWFUpload还得用作降级方案。
一、利用HTML5进行文件上传(来自Nicholas)
1、上传文件,可以点击按钮选择文件上传;也可以拖拽上传
插曲:
--------------------------------------------------------------------------------------------------------------------------------------
有一些小问题<input type="file" />在各种浏览器中的显示都不一样,
chrome:
鼠标放到按钮“选择文件”上,就会出现“未选择文件”的tips,这是chrome的特色ff:
opera:
所以需要统一下样式,简单的处理方式,就是opacity:0(隐藏掉),用单独的a元素做个按钮代替(只是样式上的代替),然后再定位。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(1)点击按钮选择文件上传
1 <input type="file" id="your-files" multiple>//multiple代表可以多文件上传
2 <script>
3 var control = document.getElementById("your-files");
4 control.addEventListener("change", function(event) {
5
6 var i = 0,
7 files = control.files,
8 len = files.length;
9
10 for (; i < len; i++) {
11 console.log("Filename: " + files[i].name);//每个文件名
12 console.log("Type: " + files[i].type);//每个文件类型
13 console.log("Size: " + files[i].size + " bytes");//每个文件大小
14 }
15
16 }, false);
17 </script>
(2)拖拽上传
1 <div id="your-files">上传文件拖拽到这里</div>
2 <script>
3 var target = document.getElementById("your-files");
4
5 target.addEventListener("dragover", function(event) {
6 event.preventDefault();
7 }, false);
8
9 target.addEventListener("drop", function(event) {
10
11 event.preventDefault();//取消默认的行为
12
13 var i = 0,
14 files = event.dataTransfer.files,
15 len = files.length;
16
17 for (; i < len; i++) {
18 console.log("Filename: " + files[i].name);
19 console.log("Type: " + files[i].type);
20 console.log("Size: " + files[i].size + " bytes");
21 }
22
23 }, false);
24 </script>
2、上传到浏览器的文件,可以利用ajax方式上传到服务器
通过FormData对象(在XMLHttpRequest Level 2中定义)可以用 Ajax方式对文件上传。该对象代替了HTML的form表单,允许你通过append()方法增加key-value的形式对上传到服务器。
1 var form = new FormData();
2 //增加的标识
3 form.append("name", "Nicholas");
4 form.append("photo", control.files[0]);
5
6 // 通过XHR上传 - 没有设头信息哦!
7 var xhr = new XMLHttpRequest();
8 xhr.onload = function() {
9 console.log("Upload complete.");
10 };
11 xhr.open("post", "/entrypoint", true);
12 xhr.send(form);
发post请求的效果,其中可以看到加的name与photo, 至于其中filename乱码的问题,还不知道是编码的原因呢?(页面用的UTF-8)还是什么?
3、读取文件
在客户端我们就可以通过FileReader来读取文件内容。
有四种读文件的方式:
(1)readAsText()---用text的方式返回文件(txt文件格式,读取没问题,但是doc这类有自己格式的文件,出来就会是乱码)
(2)readAsBinaryString()---以二进制编码的方式返回文件(已过时了,现在用readAsArrayBuffer)
(3)readAsArrayBuffer()---用一个ArrayBuffer返回文件内容(对二进制文件很有用,比如图片)
(4)readAsDataURL()---用data URL 返回文件内容
所有的这些方法启动读取文件,都类似于XHR对象的send方法启动一个HTTP请求。所以需要在开始读取文件之前监听load事件。
1 var reader = new FileReader();
2 reader.onload = function(event) {
3 var contents = event.target.result;
4 //输出文件的内容
5 console.log("File contents: " + contents);
6 };
7
8 reader.onerror = function(event) {
9 console.error("File could not be read! Code " + event.target.error.code);
10 };
11
12 reader.readAsText(file);
下面用读取URI的方式来读取图片,并利用canvas,设置它的src属性,使得图片在浏览器中呈现出来:
<canvas id="mycanvas">这里会显示上传的照片</canvas>
<script>
var reader = new FileReader();
reader.onload = function(event) {
var dataUri = event.target.result,
context = document.getElementById("mycanvas").getContext("2d"),
img = new Image();
// 等到图片被完全加载
img.onload = function() {
context.drawImage(img, 100, 100);
};
img.src = dataUri;
};
reader.onerror = function(event) {
console.error("File could not be read! Code " + event.target.error.code);
};
reader.readAsDataURL(file);//这里的file可以利用上面提到的上传文件的方式得到,比如前面的control.files[0](单个文件)
</script>
4、获取上传文件的进度
在FileReader过程中有5个事件:
(1)loadstart--表示加载数据开始。这个事件总是在最开始执行
(2)progress--当数据在加载过程中时触发,使得能够访问中间的数据
(3)error--当加载失败时触发
(4)abort---当数据加载过程被取消时触发(在XMLHttpRequest和FileReader中都能用)
(5)load---当所有的数据都成功读取以后触发
(6)loadend---当对象已经完成传输数据以后触发,总是在error,abort和load之后触发
error和load在 3、读取文件的例子中已经运用了。
在文件上传过程中,获取文件的进度信息:
(1)lengthComputable---布尔值,指示浏览器能否决定数据完整的大小
(2)loaded---已经读取的文件bytes值
(3)total---需要读取的文件的bytes值
例子如下:
1 var reader = new FileReader(),
2 progressNode = document.getElementById("my-progress");
3
4 reader.onprogress = function(event) {
5 if (event.lengthComputable) {
6 progressNode.max = event.total;//精度的最大值
7 progressNode.value = event.loaded;//进度值
8 }
9 };
10
11 reader.onloadend = function(event) {
12 var contents = event.target.result,
13 error = event.target.error;
14
15 if (error != null) {
16 console.error("File could not be read! Code " + error.code);
17 } else {
18 progressNode.max = 1;
19 progressNode.value = 1;
20 console.log("Contents: " + contents);
21 }
22 };
23
24 reader.readAsText(file);
根据loaded的进度值,就可以设置元素的背景色等,得到常见的进度条的模样。
5、处理文件错误信息
就算是上传本地文件,也会有出错的情况,在File API的说明中,定义了四种类型的文件错误。当在文件读取中发生错误时,FileReader对象错误属性将会指示四种类型错误中的一种。在实际中,浏览器用 FileError对象实现这些文件错误属性:
(1)FileError.NOT_FOUND_ERR 文件没有找到
(2)FileError.SECURITY_ERR 安全问题
(3)FileError.NOT_READABLE_ERR 不可读错误
(4)FileError.ENCODING_ERR 编码错误
(5)FileError.ABORT_ERR 当在过程中没有文件读取时,调用abort()
1 var reader = new FileReader();
2
3 reader.onloadend = function(event) {
4 var contents = event.target.result,
5 error = event.target.error;
6
7 if (error !== null) {
8 switch (error.code) {
9 case error.ENCODING_ERR:
10 console.error("Encoding error!");
11 break;
12
13 case error.NOT_FOUND_ERR:
14 console.error("File not found!");
15 break;
16
17 case error.NOT_READABLE_ERR:
18 console.error("File could not be read!");
19 break;
20
21 case error.SECURITY_ERR:
22 console.error("Security issue with file!");
23 break;
24
25 default:
26 console.error("I have no idea what's wrong!");
27 }
28 } else {
29 progressNode.max = 1;
30 progressNode.value = 1;
31 console.log("Contents: " + contents);
32 }
33 };
34
35 reader.readAsText(file);
6、利用Object URL实现从本地读取文件到浏览器中显示
Object URL是指向硬盘上文件的URL。假设,你需要在用户自己的系统中展现图片;而服务器根本不需要知道这个文件,所以就没有必要上传它。在之前的介绍中需要得到这个File对象的引用,读取数据到data URI 中,然后在 <img>元素中展现出来。但是仔细想想,图片已经在硬盘中存在了,为什么还要读取图片到另外一种形式然后来用它?如果创建一个object URL,你可以把它指派给<img>,直接从本地得到这个文件。
在File API中定义了一个全局的对象叫URL,有两个方法。
createObjectURL(),接收一个File的引用,返回一个对象URL。这个函数让浏览器创建和管理一个本地文件的URL。
reovkeObjectURL(),让浏览器销毁URL,有效的释放内存。当然所有的对象URLs都会被释放一旦网页被销毁的时候,但利用这个函数释放内存是好的编码方式,反正我们也不再需要这些对象URL。
现在浏览器对对象URL这部分的支持没有File API的其他部分支持的好。 现在Internet Explorer 10+ 和 Firefox 9+ 支持,chrome支持自己的webkitURL,Safari和Opera都不支持。
1 var URL = window.URL || window.webkitURL,
2 imageUrl,
3 image;
4
5 if (URL) {
6 imageUrl = URL.createObjectURL(file);
7 image = document.createElement("img");
8
9 image.onload = function() {
10 URL.revokeObjectURL(imageUrl);
11 };
12
13 image.src = imageUrl;
14 document.body.appendChild(image);
15 }
乍一看,貌似很强大,URL本身还不是最大的安全问题,因为URL是动态绑定到浏览器上的,在其他机器上就没用了。但是如果跨域呢?
File API不允许在不同的域中使用对象URL。当一个URL被创建,它就被绑定到执行这个JS的网页的域中,所以在www.wrox.com中使用的对象URL在prp.wrox.com中不能使用(产生error)。然而,两个都来自www.wrox.com的页面,其中一个用iframe的方式嵌入到另外一个中,就可以使用同一个对象URL。
对象URL只在文档创建它们时有效。当文档卸载,它们就都释放掉了。所以,不用担心把URL存储在本地的,以后使用;当页面卸载以后,它们就无效了。
你可以在任何地方使用对象URL,浏览器会发出一个GET请求,包括images, scripts, web workers, style sheets, audio, and video。当浏览器执行POST请求时,比如form表单中设置为post方法时,将不能使用对象URL。
7、利用Blobs分割文件
在字符串和数组中,你可能对slice()很熟悉,Blobs的作用有点类似 slice()。Blobs接收三个参数:开始的字节位移,结束的字节位移,一个可选的MIME类型。如果没有指定MIME类型,新的Blob的数据类型与之前的一样。
每一个Blob都只是代表对数据的指示,而不是数据本身,所以可以快速的创建新的Blob指向其他的子部分。需要利用slice()方法来实现。
浏览器对slice()方法的支持不是很统一,FF中是通过mozSlice()来支持的,chrome通过webkitSlice()支持。其他浏览器暂时不支持。例子:
function sliceBlob(blob, start, end, type) {
type = type || blob.type;
if (blob.mozSlice) {
return blob.mozSlice(start, end, type);
} else if (blob.webkitSlice) {
return blob.webkitSlice(start, end type);
} else {
throw new Error("This doesn't work!");
}
}
比如,你可以使用这个函数来分割一个大文件并用块的方式上传。每一个新的Blob都是独立的,即使它们之间的数据有交叉。
(1)用老方法创建 Blobs。
当文件对象在浏览器中出现,开发者意识到Blob对象非常有用,所以就想着能够不通过用户的交互来创建它。毕竟,任何数据都可以用Blob表达,而不用绑定到文件。浏览器很快就做出“响应”,创建BlobBuilder,它的目的就是为了在Blob对象中包裹数据。这不是一个标准的类型,在不同浏览器中都有不同的实现。ff(MozBlobBuilder),chrome(WebKitBlobBuilder),ie 10(MSBlobBuilder)。
BlobBuilder通过创建一个新对象,然后调用append方法,传一个字符串或者ArrayBuffer或者Blob。一旦所有的数据被加载,调用getBlob()方法,传入一个MIME类型。
例子:
1 var builder = new BlobBuilder();
2 builder.append("Hello world!");
3 var blob = builder.getBlob("text/plain");
例如,你可以用一个Blob创建一个网络线程而不需要为这个线程代码写一份单独的文件。这种技术已经存在于基本的网络线程中。
1 // Prefixed in Webkit, Chrome 12, and FF6: window.WebKitBlobBuilder, window.MozBlobBuilder(在ff中用MozBlobBuilder,在chrome中用WebKitBlobBuilder
2 var bb = new BlobBuilder();
3 bb.append("onmessage = function(e) { postMessage('msg from worker'); }");
4
5 // Obtain a blob URL reference to our worker 'file'.
6 // 注意: window.webkitURL.createObjectURL() in Chrome 10+.
7 var blobURL = window.URL.createObjectURL(bb.getBlob());
8
9 var worker = new Worker(blobURL);
10 worker.onmessage = function(e) {
console.log(e.data);//msg from worker
11 // e.data == 'msg from worker'
12 };
13 worker.postMessage('',''); // Start the worker. 需要传参数
上面的代码创建了一个简单的script脚本,然后创建了一个对象URL。对象URL被分配给一个网络线程worker.
你可以调用append()任何次数来构建Blob的内容。
(2)用新方法创建Blob
用Blob构造器来创建。第一个参数就是 老方法中append()里面的东西,第二个参数是一个对象,包括新创建的Blob的属性。目前有两个属性的定义,指向Blob的MIME类型和结尾(可以是transparent默认或者是native)
1 var blob = new Blob(["Hello world!"], { type: "text/plain" });
这种方法暂时只在chrome中支持。ff13将会支持。
结束:
使用这些技术,你可以在图片上传前,缩放图片的大小(使用FileReader和canvas);你可以创建一个纯浏览器端的文件编辑器;你可以分割大的文件一块一块的上传。这些可能性都是无止境的,它们已经离我们越来越近了。