Html5終於解決了上傳文件的同時顯示文件上傳進度的老問題。現在大部分的網站用Flash去實現這一功能,還有一些網站繼續采用Html <form>with enctype=multipart/form-data,但是需要修改服務器端可用才能顯示給用戶文件上傳的進度。本質上你需要做的工作是在服務器端接收一個文件時,你發送給它一個字節流,所以你需要知道你已經接收到多少字節並以某種方式傳達這些信息給客戶端瀏覽器,在這個過程一直在不斷的進行文件的上傳。這種方式運行的非常好,不像Flash上傳那這樣充滿了問題(特別是處理大文件上傳的時候),然而這種方法是相當復雜的並且聽起來不容易理解,因為你本質上是接管了整個服務器端的處理(獲取字節流的時候)同時包括了在服務器端實現multipart/form-data協議,伴隨一系列的其他事情。
使用Html5 上傳文件
XMLHttpRequest 在Html5 規范中已經有全新的變化,規定了XMLHttpRequest Level 2規范(目前最新版本)包含下列新的特性:
- 處理字節流,例如作為上傳或者下載的File,Blob,FormData對象
- 上傳或者下載中的進度事件
- 跨站點請求
- 允許創建匿名請求
- 可以設置請求超時
在這篇文章中我們將能夠更清楚的看到#1和#2兩個特性。通常,上傳文件用XMLHttpRequest並且提供上傳進度信息給最終的用戶,需要注意的是這種方式解決了不需要服務器端做任何改變,至少是目前處理multipart/form-data協議。所以服務器端的處理邏輯保留不變,這使得開發者適應這種技術相當容易。
圖1:文件上傳畫面-准備上傳
圖2:顯示上傳完成畫面
注意:上面的圖片中,信息提示區域是提供給用戶的:
- 當前選中文件的信息
- 文件名
- 文件大小
- 文件類型
- 上傳完成多少的百分比進度條
- 上傳速度或者上傳帶寬
- 距離上傳完成大概還有多長時間
- 已上傳文件大小
- 服務器端的響應
上面第6項或許看起來不重要,但事實上是相當重要的。因為我們用XMLHttpRequest,上傳發生在后台,頁面沒有發生跳轉等任何變化,所以對於你用它處理其他一些事情來說是一個非常好的特性。
Html5 Progress Event
對於Html5 Progress Events規范,Html5 Progess Events提供了下列與本次討論相關的信息
- total - 總的字節數
- loaded - 到目前為止上傳的字節數
- lengthComputable - 可計算的已上傳字節
請注意到我們需要用兩個信息去計算要顯示給用戶的其他所有信息。要計算出來其他的信息通過上面我們得到信息是相當容易的,但是那需要一些額外的代碼並且創建一個定時器。
Html5 Progress Event 應該是什么
考慮到有一部分人想更好的提供給用戶所有的信息,所以Html5 Progress Event應該更好的滿足需要,因為它給瀏覽器供應商提供這些額外信息是相當簡單的,所以建議progress event應該修改成如下:
- total - 總的字節數
- loaded - 到目前為止上傳的字節數
- lengthComputable - 可計算的已上傳字節
- transferSpeed long類型
- timeRemaining JavaScript 日期對象
Html5 上傳 用XMLHttpRequest
瀏覽器支持情況
支持這一特性的瀏覽器最低版本
- Firefox 4.0 beta 6
- Chrome 6
- Safari 5.02
IE 9 Beta and Opera 10.62 不支持這一特性
簡單的示例
下面是一個完整的Html頁面包含了實現文件上傳並帶有進度提示的JavaScript代碼,只是實現了基本的功能,感興趣的可以自己做擴展。 需要吧上傳接口修改成自己服務的。
<!DOCTYPE html>
<html>
<head>
<title>Upload Files using XMLHttpRequest - Minimal</title>
<script type="text/javascript">
function fileSelected() {
var file = document.getElementById('fileToUpload').files[0];
if (file) {
var fileSize = 0;
if (file.size > 1024 * 1024)
fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
else
fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
}
}
function uploadFile() {
var fd = new FormData();
fd.append("fileToUpload", document.getElementById('fileToUpload').files[0]);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "upload.do");//修改成自己的接口
xhr.send(fd);
}
function uploadProgress(evt) {
if (evt.lengthComputable) {
var percentComplete = Math.round(evt.loaded * 100 / evt.total);
document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
}
else {
document.getElementById('progressNumber').innerHTML = 'unable to compute';
}
}
function uploadComplete(evt) {
/* 服務器端返回響應時候觸發event事件*/
alert(evt.target.responseText);
}
function uploadFailed(evt) {
alert("There was an error attempting to upload the file.");
}
function uploadCanceled(evt) {
alert("The upload has been canceled by the user or the browser dropped the connection.");
}
</script>
</head>
<body>
<form id="form1" enctype="multipart/form-data" method="post" action="Upload.aspx">
<div class="row">
<label for="fileToUpload">Select a File to Upload</label><br />
<input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected();"/>
</div>
<div id="fileName"></div>
<div id="fileSize"></div>
<div id="fileType"></div>
<div class="row">
<input type="button" onclick="uploadFile()" value="Upload" />
</div>
<div id="progressNumber"></div>
</form>
</body>
</html>
