人人網首頁拖拽上傳詳解(HTML5 Drag&Drop、FileReader API、FormData)

http://fed.renren.com/archives/391


早在公元2011年6月3日傍晚,人人網推出了一個很裝B且完全無視IE瀏覽器的功能——拖拽上牀。哦,Sorry, 是拖拽上傳。本文將重點介紹實現拖拽上傳的幾個HTML5技術:Drag&DropFileReader APIFormData

  關於這個拖拽上傳,其實國外有很多網站已經有這樣的應用,最早推出拖拽上傳應用的是Gmail,它支持標準瀏覽器下拖拽本地文件到瀏覽器中作爲郵件的附件發送。人人網的這個拖拽上傳也是同理,可以讓使用標準瀏覽器的用戶通過簡單的拖拽行爲,將本地文件夾中的照片直接上傳到人人網,用戶體驗能得到提升的同時,也希望藉此機會推廣一下標準瀏覽器,淘汰IE。人人網當時也向廣大用戶推出升級瀏覽器活動,並喊出口號:”工欲善其計算機,必先利其瀏覽器”。本次拖拽上傳的宣傳口號:你敢”脫”,我就敢上傳…

人人網 - 拖拽上傳

  言歸正題,首先看看效果,大家如果有人人網帳號的話可以在首頁試一試拖拽上傳功能,下面是演示視頻:

拖拽上傳應用主要使用了以下HTML5技術:

  • Drag&Drop : HTML5基於拖拽的事件機制.
  • File API :  可以很方便的讓Web應用訪問文件對象,File API包括FileListBlobFileFileReaderURI scheme,本文主要講解拖拽上傳中用到的FileList和FileReader接口。
  • FormData : FormData是基於XMLHttpRequest Level 2的新接口,可以方便web應用模擬Form表單數據,最重要的是它支持文件的二進制流數據,這樣我們就能夠通過它來實現AJAX向後端發送文件數據了。

HTML5 Drag&Drop 拖拽事件

  關於Drag&Drop拖拽事件,之前我寫過一篇專門介紹的文章《給力的 Google HTML5 訓練營(HTML5 Drag&Drop 拖拽、FileReader實例教程)》,那篇文章詳細講解了Drag & Drap事件的原理和代碼實例,這裏的拖拽上傳實現原理基本上是一樣的,大家有興趣或不太瞭解的話可以先看看那篇文章,我在這裏就不再過多囉嗦了~下面直接出拖拽上傳簡要代碼實例:

var oDragWrap = document.body;

//拖進
oDragWrap.addEventListener(‘dragenter’, function(e) {
 e.preventDefault();
}, false);

//拖離
oDragWrap.addEventListener(‘dragleave’, function(e) {
 dragleaveHandler(e);
}, false);

//拖來拖去 , 一定要注意dragover事件一定要清除默認事件
//不然會無法觸發後面的drop事件
oDragWrap.addEventListener(‘dragover’, function(e) {
 e.preventDefault();
}, false);

//扔
oDragWrap.addEventListener(‘drop’, function(e) {
 dropHandler(e);
}, false);

var dropHandler = function(e) {
//將本地圖片拖拽到頁面中後要進行的處理都在這
}

獲取文件數據 HTML5 File API

  在之前那篇文章中我也有介紹過關於File API中的FileReader接口,作爲 File API 的一部分,FileReader 專門用於讀取文件,根據 W3C 的定義,FileReader 接口 “提供一些讀取文件的方法與一個包含讀取結果的事件模型”。關於FileReader的詳細介紹和代碼實例大家可以先去看看那篇文章

  今天我着重介紹一下File API中的FileList接口,它主要通過兩個途徑獲取本地文件列表,一是<input type=”file”>的表單形式,另一種則是e.dataTransfer.files拖拽事件傳遞的文件信息。很顯然,我們這裏會用到後者。

var fileList = e.dataTransfer.files;

  使用files方法將會獲取到拖拽文件的數組形勢的數據,每個文件佔用一個數組的索引,如果該索引不存在文件數據,將返回null值。可以通過length屬性獲取文件數量.

var fileNum = fileList.length;

  拖拽上傳需要注意的是需要判斷兩個條件,1:拖拽的是文件不是頁面中的元素; 2:拖拽的是圖片而不是其它文件,可以通過file.type屬性獲取文件的類型

//檢測是否是拖拽文件到頁面的操作
if (fileList.length === 0) {return;};
//檢測文件是不是圖片
if (fileList[0].type.indexOf(‘image’) === -1) {return;}

  下面讓我們來看看如何結合之前的拖拽事件來實現拖拽圖片並在頁面中進行預覽:

var dropHandler = function(e) {
 e.preventDefault();

 //獲取文件列表
 var fileList = e.dataTransfer.files;

 //檢測是否是拖拽文件到頁面的操作
 if (fileList.length == 0) {return;};

 //檢測文件是不是圖片
 if (fileList[0].type.indexOf(‘image’) === -1) {return;}

 //實例化file reader對象
 var reader = new FileReader();
 var img = document.createElement(‘img’);

 reader.onload = function(e) {
  img.src = this.result;
  oDragWrap.appendChild(img);
 }
 reader.readAsDataURL(fileList[0]);

}

  這裏有一個簡單的拖拽圖片預覽的Demo

  這時你如果用FireBug等類似調試工具查看DOM的話,會看到<img>標籤的src屬性是一個超長的文件二進制數據,所以如果DOM有很多這類圖片,那就要當心瀏覽器性能了,因爲這些數據極大地擴充的頁面的代碼量,而每次頁面的reflow都會對瀏覽器形成很大的負擔,So,如果這些圖片還在DOM中,那就儘量不要做動畫或任何重繪操作,如果真的要做就儘量讓圖片脫離文檔流,讓其絕對定位比較靠譜。

補充:可以使用window.URL.createObjectURL(file)來獲取文件的URL(Chrome下用window.webkitURL.createObjectURL(file)),這種方式獲取的URL要比上面說的readAsDataURL簡短很多。而且可以省去使用FileReader。這裏感謝BinBinLiao的留言建議:) 下面是使用readAsDataURL與createObjectURL生成的代碼對比:

readasdataurl-vs-createobjecturl

優化後的代碼:(紅色爲優化的代碼)

var dropHandler = function(e) {
 e.preventDefault();

 var fileList = e.dataTransfer.files;  //獲取文件列表
 var img = document.createElement(‘img’);

 //檢測是否是拖拽文件到頁面的操作
 if (fileList.length == 0) {return;};

 //檢測文件是不是圖片
 if (fileList[0].type.indexOf(‘image’) === -1) {return;}
 

 if (window.URL.createObjectURL) {
  //FF4+
  img.src = window.URL.createObjectURL(fileList[0]);
 } else if (window.webkitURL.createObjectURL) {
  //Chrome8+
  img.src = window.webkitURL.createObjectURL(fileList[0]);
 } else {
  //實例化file reader對象
  var reader = new FileReader();

  reader.onload = function(e) {
   img.src = this.result;
   oDragWrap.appendChild(img);
  }
  reader.readAsDataURL(fileList[0]);
 }

}

  需要注意的是,window.URL.createObjectURL是有生命週期的,也就意味着你每用此方法獲取URL,其生命週期都會和DOM一樣,它會單獨佔用內存,所以當刪除圖片或不再需要它是,記得用window.URL.revokeObjectURL(file)來釋放其內存。當然,如果你沒有釋放,刷新頁面也是可以釋放的。

AJAX上傳圖片(file.getAsBinary & FormData)

  既然已經獲取到了拖拽到web頁面中圖片的數據,下一步就是將其發送到服務器端了。

  話說HTML5時代之前,AJAX傳輸文件二進制流數據是不可能完成的事情,而現在我們完全可以通過file.getAsBinary獲取文件的二進制數據流,進而將其當做XHR的data數據傳送到後端,8過由於Chrome不支持file的getAsBinary方法,FF3.6+支持此方法。所以Chrome就要另尋它法了,這時我們發現XMLHttpRequest Level 2中的FormData接口完美解決了這個問題,它可以很快捷的模擬Form表單數據並通過AJAX發送至後端,FormData的支持情況是FF5及以上支持,Chrome12及以上支持。

   file.getAsBinary獲取文件流很簡單,但是要想上傳數據,就要模擬一下表單的數據格式了,首先看看模擬表單的js代碼, FormData模擬表單數據時更是簡潔,不用麻煩的去拼字符串,而是直接將數據append到formdata對象中即可:

var xhr = new XMLHttpRequest();
var url = ‘http://upload.renren.com/……’;
var boundary = ‘———————–’ + new Date().getTime();
var fileName = file.name;

xhr.open(“post”, url, true);
xhr.setRequestHeader(‘Content-Type’, ‘multipart/form-data; boundary=’ + boundary);

if (window.FormData) {
 //Chrome12+
 var formData = new FormData();
 formData.append(‘file’, file);
 formData.append(‘hostid’, userId);
 formData.append(‘requestToken’, t);

 data = formData;
} else if (file.getAsBinary) {
 //FireFox 3.6+
 data = “–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”" +
 ’file’ +
 ”\”; ” +
 ”filename=\”" +
 unescape(encodeURIComponent(file.name)) +
 ”\”" +
 crlf +
 ”Content-Type: image/jpeg” +
 crlf +
 crlf +
 file.getAsBinary() +
 crlf +
 ”–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”hostid\”" +
 crlf +
 crlf +
 userId +
 crlf +
 ”–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”requestToken\”" +
 crlf +
 crlf +
 t +
 crlf +
 ”–” +
 boundary +
 ’–’;
}

xhr.send(data);

首先表單數據headers頭信息需要以下兩項:

  • Content-Type : 設置其爲multipart/form-data來模擬表單數據
  • boundary : 表單數據中的分隔符,用於分隔不同的文件或表單項,這是服務器端設置的格式。

發送時的post數據類似這樣:

————————-1323611763556
Content-Disposition: form-data; name=”file”; filename=”4.jpg”
Content-Type: image/jpeg

ÿØÿà�JFIF�…這裏是文件二進制流…~iúoî­5P%-vãîHü 4QHgÿÙ
————————-1323611763556
Content-Disposition: form-data; name=”hostid”

229421603
—————————–1323612996486

Content-Disposition: form-data; name=”requestToken”

369009193
————————-1323611763556–

好了,現在文件上傳成功後你就可以按照平常AJAX的操作來進行後續處理了。

最後,再來總結一下拖拽上傳的技術要點:

  1. 監聽拖拽:監聽頁面元素的拖拽事件,包括:dragenter、dragover、dragleave和drop,一定要將dragover的默認事件取消掉,不然無法觸發drop事件。如需拖拽頁面裏的元素,需要給其添加屬性draggable=”true”;
  2. 獲取拖拽文件:在drop事件觸發後通過e.dataTransfer.files獲取拖拽文件列表,.length屬性獲取文件數量,.type屬性獲取文件類型。
  3. 讀取圖片數據並添加預覽圖:實例化FileReader對象,通過其readAsDataURL(file)方法獲取文件二進制流,並監聽其onload事件,將e.result賦值給img的src屬性,最後將圖片append到DOM中。
  4. 發送圖片數據:使用file.getAsBinaryFormData分別模擬表單數據AJAX提交文件流。 

發佈了5 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章