檔案上傳的漸進式增強

阮一峰發表於2012-08-10

檔案上傳是最古老的網際網路操作之一。

20多年了,它幾乎沒變,還是原來的樣子:操作麻煩、缺乏互動、使用者體驗不佳。在這個新技術日新月異的時代,顯得非常落伍。

檔案上傳的漸進式增強

網頁開發者們想了很多辦法,試圖提升檔案上傳的功能和操作體驗,在各種Javascript庫的基礎上,開發了五花八門的外掛。可是,由於不同瀏覽器之間的差異,缺乏統一介面,這些外掛要麼用起來很麻煩,要麼不能普遍適用。

HTML5提供了一系列新的瀏覽器API,使得檔案上傳有可能出現革命性變化。英國程式設計師Remy Sharp總結了這些新的介面,本文在他的文章基礎上,討論如何採用HTML5的API,對檔案上傳進行漸進式增強,實現以下功能:

  * iframe上傳

  * ajax上傳

  * 進度條

  * 檔案預覽

  * 拖放上傳

為了對這些功能有一個感性認識,你可以先看看Remy Sharp提供的範例

雖然這些API,還沒有得到廣泛部署,但它們是未來的潮流。有了它們,程式碼就可以寫得非常優雅簡潔,上面五個功能都能在20行以內實現。

檔案上傳的漸進式增強

一、傳統形式

讓我們從最基本的開始。

檔案上傳的傳統形式,是使用表單元素file:

  <form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >

    <input type="file" id="upload" name="upload" /> <br />

    <input type="submit" value="Upload" />

  </form>

所有瀏覽器都支援上面的程式碼。它在IE瀏覽器中,顯示如下:

檔案上傳的漸進式增強

使用者先選擇檔案,然後點選"Upload"按鈕,檔案開始上傳。

二、iframe上傳

傳統的表單上傳,屬於"同步上傳"。也就是說,點選上傳按鈕後,網頁"鎖死",使用者只能等待上傳結束,然後瀏覽器重新整理,跳到表單的action屬性指定的網址。

有沒有辦法"非同步上傳",在網頁不過載的情況下,完成整個上傳過程呢?

在HTML5沒有出現之前,只能使用iframe做到這一點。使用者點選submit時,動態插入一個iframe元素(以下程式碼使用了jQuery函式庫)。

  var form = $("#upload-form");

  form.on('submit',function() {

    // 此處動態插入iframe元素

  });

插入iframe的程式碼如下:

  var seed = Math.floor(Math.random() * 1000);

  var id = "uploader-frame-" + seed;

  var callback = "uploader-cb-" + seed;

  var iframe = $('<iframe id="'+id+'" name="'+id+'" style="display:none;">');

  var url = form.attr('action');

  form.attr('target', id).append(iframe).attr('action', url + '?iframe=' + callback);

最後一行,有兩個地方值得注意。首先,它為表單新增target屬性,指向動態插入的iframe視窗,這使得上傳結束後,伺服器將結果返回iframe視窗,所以當前頁面就不會跳轉了。其次,它在action屬性指定的上傳網址的後面,新增了一個引數,使得伺服器知道回撥函式的名稱。這樣就能將伺服器返回的資訊,從iframe視窗傳到上層頁面。

伺服器(upload.php)返回的資訊,應該是如下形式:

  <script type="text/javascript">

    window.top.window['callback'](data);

  </script>

然後,在當前網頁定義回撥函式:

  window[callback] = function(data){

    console.log('received callback:', data);

    iframe.remove(); //removing iframe

    form.removeAttr('target');

    form.attr('action', url);

    window[callback] = undefined; //removing callback

  };

三、ajax上傳

HTML5提出了XMLHttpRequest物件的第二版,從此ajax能夠上傳檔案了。這是真正的"非同步上傳",是將來的主流。上一節的iframe上傳,可以用作老式瀏覽器的替代方案。

ajax上傳程式碼,放在表單的submit事件回撥函式中:

  form.on('submit',function() {

    // 此處進行ajax上傳

  });

我們主要用的是FormData物件,它能夠構建類似表單的鍵值對。

  // 檢查是否支援FormData
  if(window.FormData) { 

    var formData = new FormData();

    // 建立一個upload表單項,值為上傳的檔案
    formData.append('upload', document.getElementById('upload').files[0]);

    var xhr = new XMLHttpRequest();

    xhr.open('POST', $(this).attr('action'));

    // 定義上傳完成後的回撥函式
    xhr.onload = function () {

      if (xhr.status === 200) {

        console.log('上傳成功');

      } else {

        console.log('出錯了');

      }

    };

    xhr.send(formData);

  }

四、進度條

XMLHttpRequest第二版還定義了一個progress事件,可以用來製作進度條。

首先,在頁面中放置一個HTML元素progress。

  <progress id="uploadprogress" min="0" max="100" value="0">0</progress>

然後,定義progress事件的回撥函式。

  xhr.upload.onprogress = function (event) {

    if (event.lengthComputable) {

      var complete = (event.loaded / event.total * 100 | 0);

      var progress = document.getElementById('uploadprogress');

      progress.value = progress.innerHTML = complete;

    }

  };

注意,progress事件不是定義在xhr,而是定義在xhr.upload,因為這裡需要區分下載和上傳,下載也有一個progress事件。

五、圖片預覽

如果上傳的是圖片檔案,利用File API,我們可以做一個圖片檔案的預覽。這裡主要用到FileReader物件

  // 檢查是否支援FileReader物件
  if (typeof FileReader != 'undefined') {

    var acceptedTypes = {
      'image/png': true,
      'image/jpeg': true,
      'image/gif': true
    };

    if (acceptedTypes[document.getElementById('upload').files[0].type] === true) {

      var reader = new FileReader();

      reader.onload = function (event) {

        var image = new Image();

        image.src = event.target.result;

        image.width = 100;

        document.body.appendChild(image);

      };

    reader.readAsDataURL(document.getElementById('upload').files[0]);

    }

  }

六、拖放上傳

最後,利用HTML5的拖放功能,實現拖放上傳。

先在頁面中放置一個容器,用來接收拖放的檔案。

  <div id="holder"></div>

對它設定樣式:

  #holder {
    border: 10px dashed #ccc;
    width: 300px;
    min-height: 300px;
    margin: 20px auto;
  }

  #holder.hover {
    border: 10px dashed #0c0;
  }

拖放檔案的程式碼,主要是定義dragover、dragend和drop這三個事件。

  // 檢查瀏覽器是否支援拖放上傳。
  if('draggable' in document.createElement('span')){

    var holder = document.getElementById('holder');

    holder.ondragover = function () { this.className = 'hover'; return false; };

    holder.ondragend = function () { this.className = ''; return false; };

    holder.ondrop = function (event) {

      event.preventDefault();

      this.className = '';

      var files = event.dataTransfer.files;

      // do something with files

    };

  }

完成後的效果和總體程式碼,請看拖放上傳demo

(完)

相關文章