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

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

完成頁面: Drop upload demo
dragupload

申請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判斷等等。但擔心繼續寫下去會太長,等下一篇再來處理這些小細節吧。