Upload progress bar drag&drop 圖片拖拉上傳進度條

Upload progress bar drag&drop 圖片拖拉上傳進度條

October 27, 2018

·

9 min read

最近剛好要使用圖片上傳的功能,但因為資源較少,所以考慮用網路上的 image api 服務,稍微研究後選擇 imgur 的服務。以前拖拉上傳,都是用套件處理掉,剛好來研究一下如何處理拖拉上傳圖片,附加進度條功能。

我們需要取得 imgur api,再來使用 input 處理上傳檔案,檔案串接上傳 imgur api,在處理 drag and drop 拖拉上傳檔案,完成後也要串接 imgur api。

完成頁面: Drop upload demo

申請 imgur api key

首先進入 imgur 申請帳號登入,再點選下方的連結,申請屬於自己的 Client ID。後面 call api 會需要使用。

imgur Register an Application

查看 imgur 提供的 restful api post image,需要帶的有 header client ID,body 則需要有格式 image A binary file, base64 data, URL for image.限制 10MB 以下,其他則是選填 album(optional)、title、description、name、type。

imgur upload api post

  • imgur jquery ajax sample
var form = new FormData();
form.append(
  "image",
  "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
);

var settings = {
  async: true,
  crossDomain: true,
  url: "https://api.imgur.com/3/image",
  method: "POST",
  headers: {
    Authorization: "Client-ID {{clientId}}",
  },
  processData: false,
  contentType: false,
  mimeType: "multipart/form-data",
  data: form,
};

$.ajax(settings).done(function (response) {
  console.log(response);
});

傳統 input 上傳

先簡易的傳統的點擊 input 上傳檔案,首先創建 input tag,type 選擇 file,再來監聽這個 input 有沒有變化,如果他有變化的話,代表獲取到檔案上傳,我們就要轉出這個檔案。

對了,官網範例是用 jquery ajax,為了方便我們直接拿來用,記得要引入 jquery library。

...
<body>
  <input type="file" accept="image/*" id="imageUpload" />
  <img id="imagePreview">
  <script>
    // listen input change call handleFiles
    document.querySelector('#imageUpload').addEventListener('change',handleFiles);

    function handleFiles() {
      // get input files object
      var fileList = this.files;
      console.log(fileList);
      if (this.files[0]) {
        // call ajax
        upload(this.files[0]);
      }
    }
...

接到檔案後,直接用官網的 sample,調用 ajax post api,headers 帶 Authorization 用產生的 client id,以檔案作為參數。

完成上傳後,api 會回傳 json 的 string type,所以接到 api 回傳後,要用 JSON.parse 轉成 json type,再取出 link 圖片的網址。這樣就完成了 imgur 的 api 串接。

...
    function upload(data) {
      var form = new FormData();
      form.append("image", data);

      var settings = {
        "async": true,
        "crossDomain": true,
        "url": "https://api.imgur.com/3/image",
        "method": "POST",
        "headers": {
          "Authorization": "Client-ID {{clientId}}"
        },
        "processData": false,
        "contentType": false,
        "mimeType": "multipart/form-data",
        "data": form
      }

      $.ajax(settings).done(function (response) {
        // get respon string type json
        var res = JSON.parse(response);
        console.log(res.data.link);
        document.getElementById("imagePreview").src = res.data.link;
      });
    }
  </script>
</body>
  • input upload imgur demo

上傳檔案 progress bar

製作上傳進度條,就需要監聽上傳檔案的進度,在 jquey ajax 在增加 option xhr,延展 jquery 具有 xhr 特性,在使用 xhr 監聽 upload 事件,取得目前上傳檔案的 size,在除以整個檔案後取的比例,再將這個比例帶進 progress bar value,這樣就完成了上傳進度條。

提醒 fetch 無法使用進度條監聽事件,axios、原生 xmlhttprequest 都有方法實作進度條。

實作方法 : axios onUploadProgressxmlhttprequest mdn progress bar

  // add progress bar element
  <progress id="progress" style="display:none" value="0" max="100"></progress>
  <img id="imagePreview" onload="hideProgress()">
...
        "mimeType": "multipart/form-data",
        "data": form,
        // add xhr to extend ajax
        "xhr": function() {
            var myXhr = $.ajaxSettings.xhr();
            if(myXhr.upload){
                myXhr.upload.addEventListener('progress',progress, false);
            }
            return myXhr;
        },
...
function progress(e){
  if(e.lengthComputable){
    var length = e.total;
    var current = e.loaded;
    var progress = document.getElementById('progress');

    var percent = Math.floor((current * 100)/length);
    progress.value = percent;
    if (progress.style.display !== 'block'){
      progress.style.display = 'block';
    }
  }
 }
 function hideProgress(){
    document.getElementById('progress').style.display = 'none';
 }
  • upload progress version

拖拉上傳檔案

這邊會是技術比較複雜的部分,一般網頁開發比較少用到 drag,首先要了解拖拉物件,會觸發哪些 event,拖拉過程怎麼帶資料互動,

稍微測試每個事件觸發點,當我開始拖拉一個物件,會先觸發 dragstart => drag => dragenter / dragover / dragleave / dragexit => drop => dropend,也就是說拖拉物件到放置另一個區塊,會觸發 drop、dropend,其中 drop 會再被放置到有效區塊才會觸發,而且 dragend 無法以外部拖拉檔案觸發,所以我們只要監聽 drop 就好了。

  • HTML drag API
drag 於一個元素或文字選取區塊被拖曳時觸發。
dragend	於拖曳操作結束時觸發(如放開滑鼠按鍵或按下鍵盤的 escape 鍵。
dragenter 於一個元素或文字選取區塊被拖曳移動進入一個有效的放置目標時觸發。
dragexit  當一個元素不再是被選取中的拖曳元素時觸發。
dragleave 於一個元素或文字選取區塊被拖曳移動離開一個有效的放置目標時觸發。
dragover  於一個元素或文字選取區塊被拖曳移動經過一個有效的放置目標時觸發
dragstart 於使用者開始拖曳一個元素或文字選取區塊時觸發。
drop 於一個元素或文字選取區塊被放置至一個有效的放置目標時觸發。

注意:dragstart 與 dragend 事件,在把檔案從作業系統拖放到瀏覽器時,並不會觸發。

drag 文件: MDN HTML Drag_and_Drop_API

  • MDN drag element demo

外部檔案拖拉

如果是外部檔案拖拉進的話,依序觸發 dragenter => dragover => drop,這幾個動作要取消預設的事件 event.preventDefault(),防止拖拉開啟檔案。可以用 event.dataTransfer.files 來接受拖拉的檔案。

下面範例用 FileReader 來轉換檔案成 base64 格式,在打印到 img src 上顯示圖片。

<div id="dragZone">
<img src="https://fakeimg.pl/450x100/?text=Drop_your_Image">
</div>
<div id="fileText"></div>
<img src="" id="fileImg" />

<script>
var dragZone = document.getElementById('dragZone');
var fileText = document.getElementById('fileText');
var fileImg = document.getElementById('fileImg');
// cancel default event
dragZone.addEventListener('dragover',function(e){
  e.preventDefault();
  console.log('dragover')
});
dragZone.addEventListener('drop',function(e){
  // cancel default event
  e.preventDefault();
  if(e.dataTransfer.files.length > 1){
    alert('僅支援單一檔案');
    return;
  }
  var file = e.dataTransfer.files[0];
  if(file.type.indexOf('image') === -1){
    alert('僅支援圖片格式喔');
    return;
  }
  fileText.innerText = file.name;
  // export file as base64
  var reader = new FileReader();
   reader.readAsDataURL(file);
   reader.onload = function () {
     fileImg.src = reader.result
   };
});
</script>

這樣就幾乎完成了。我們只要把上面的 drop 獲取檔案,一起與 ajax 呼叫,就支援拖拉上傳檔案了。

首先增加包覆的 div,隱藏掉傳統的 input 按鈕,讓外層的區塊點擊觸發點擊 input。再增加上事件監聽,主要依靠 drop 監聽,當獲取檔案時,調用 ajax 傳輸資料。

  • 修改先前檔案
  // add container zone and hide input
  <div id="dragZone">
    拖拉上傳 Drop Upload
    <input type="file" accept="image/*" id="imageUpload" />
    <progress id="progress" style="display:none" value="0" max="100"></progress>
  </div>
...
var dragZone = document.getElementById('dragZone');

// add listener click dropzone trigger click input
dragZone.addEventListener('click',function(e){
  document.getElementById('imageUpload').click();
})
dragZone.addEventListener('dragover',function(e){
  e.preventDefault();
});

dragZone.addEventListener('drop',function(e){
...
  if(file.type.indexOf('image') === -1){
    alert('僅支援圖片格式喔');
     return;
  }
  // get file call ajax
  upload(file);
});

頁面完成

Drop file demo

成功!!這樣就完成拖拉上傳了。

實際寫一遍會發現其實並沒有太艱難,只是有滿多小細節要處理,包含拖拉事件、串接 api 檔案格式、進度條處理等等。這邊還沒寫邏輯還有圖片寬度高度、size 判斷等等。但擔心繼續寫下去會太長,等下一篇再來處理這些小細節吧。