文件上傳_HTML5與SWFUpload(1)


要實現文件上傳,要兼顧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:

鼠標放到按鈕“選擇文件”上,就會出現“未選擇文件”的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);你可以創建一個純瀏覽器端的文件編輯器;你可以分割大的文件一塊一塊的上傳。這些可能性都是無止境的,它們已經離我們越來越近了。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM