文件上传_HTML5与SWFUpload(1)

时间:2022-02-19 04:13:31

要实现文件上传,要兼顾IE6份额还不小的市场情况,除了fashion的HTML5,SWFUpload还得用作降级方案。

Nicholas有一个系列关于利用HTML5上传文件的博客“Working with files in JavaScript”,另外,这里还有一篇也很详细的利用HTML5上传文件的文章(中文版哦)http://www.html5rocks.com/zh/tutorials/file/dndfiles/#toc-slicing-files

一、利用HTML5进行文件上传(来自Nicholas)

1、上传文件,可以点击按钮选择文件上传;也可以拖拽上传

插曲:

--------------------------------------------------------------------------------------------------------------------------------------

有一些小问题<input type="file"  />在各种浏览器中的显示都不一样,

chrome:

文件上传_HTML5与SWFUpload(1)鼠标放到按钮“选择文件”上,就会出现“未选择文件”的tips,这是chrome的特色

ff:
文件上传_HTML5与SWFUpload(1)

opera:

文件上传_HTML5与SWFUpload(1)

 所以需要统一下样式,简单的处理方式,就是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)还是什么?
文件上传_HTML5与SWFUpload(1)

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);你可以创建一个纯浏览器端的文件编辑器;你可以分割大的文件一块一块的上传。这些可能性都是无止境的,它们已经离我们越来越近了。