iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
Modern Web

前端蛇行撞牆記系列 第 24

Day24 前端蛇行撞牆記 - 製作簡易的檔案拖拉上傳 / drag and drop

  • 分享至 

  • xImage
  •  

前言

今天要來做一個可以使用 input 上傳檔案,也可以 drag and drop 檔案的範例~

不過現在做的只是可以拿到檔案的名稱顯示而已喔!

input 上傳檔案:

drag and drop拖拉上傳檔案:

input[type="file"]

input的type="file"是可以上傳檔案的,不過因為他本人長得有點抱歉,而且也不太能改動,所以我會把他隱藏起來再另外做一個。

html:

<div class="container">
  <input class="file-upload-input" name="upload-file" type="file" />

  <div class="file-upload-zone">
    <a href="#" class="file-upload__btn">upload file</a>
    <span class="file-upload__name"></span>
  </div>
</div>

先把input { display: none }
再另外做一個 <a> 連結來做一個上傳檔案的button樣式,不過我們依然需要他被點擊打開資料夾選擇檔案的畫面。

這邊學到可以利用 HTMLElement.click() 直接在<a> 的 click 事件裡面再去 input.click() ,就可以等於點擊到被隱藏的 input

MDN:

HTMLElement.click() 方法可以模擬滑鼠點擊一個元素。
當 click() 被使用在支援的元素(例如任一 <input> 元素),便會觸發該元素的點擊事件。事件會冒泡至 document tree(或 event chain)的上層元素,並觸發它們的點擊事件。其中的例外是:click() 方法不會讓 <a> 元素和接收到真實滑鼠點擊一樣進行頁面跳轉。

最後一句話 click() 方法不會讓 <a> 元素和接收到真實滑鼠點擊一樣進行頁面跳轉。」 所以在這裡不需要使用 e.preventDefault() 來防止 <a> 的預設跳轉事件。

JavaScript:

const fileInput = document.querySelector(".file-upload-input");
const fileBtn = document.querySelector(".file-upload__btn");

fileBtn.addEventListener("click", function (e) {
    fileInput.click();
});
  • input[type="file"]選起來 => fileInput
  • 將自製的<a>按鈕也選起來 => fileBtn
  • 對fileBtn做一個click監聽事件
  • 監聽事件的handler再去對 fileInput.click()

input[type="file"] 如何拿到檔案的名稱

雖然是按假的按鈕,實際上還是在操作 input ,而input[type='file']的監聽事件是 change ,只要有新檔案進來對input來說就是一個改變。

所以還是必須對 input 來做監聽事件,才能拿到新檔案的名稱。

記得昨天提到的Listener的參數 event 有一堆屬性嗎?檔案名稱也可以從裡面拿到,不過這只有input[type='file']專屬的屬性,所以要用event.target去拿到。

input's HTMLInputElement.files

來試試看把 e.target.files 印出來看看長怎樣:

看起來是個類陣列,也有檔案的名稱顯示在裡面,就可以使用files[0].name來拿到檔案的名稱,再把這個名稱放到要呈現檔案名稱的tag就好了!

const fileInput = document.querySelector(".file-upload-input");
const fileNameZone = document.querySelector(".file-upload__name");

fileInput.addEventListener("change", function (e) {
    const fileName = e.target.files[0].name;
    fileNameZone.textContent = fileName;
});

成果:

drag and drop

drag, drop 也是監聽事件的名稱,但是分為好幾種,這邊只列出這次會用到的部分:

事件 會如何被觸發
dragenter The dragged item is dragged over dropArea, making it the target for the drop event if the user drops it there. 當被拖拉的項目經過有drop監聽事件的區域,當這個項目被drop下來就會當作目標
dragleave The dragged item is dragged off of dropArea and onto another element, making it the target for the drop event instead . 當被拖拉的項目離開有drop監聽的區域或是拖拉到其他地方的時候觸發,會讓這個項目變成要被drop的目標
dragover Every few hundred milliseconds, while the dragged item is over dropArea and is moving. 每一百毫秒觸一次,當被拖拉的項目經過drop地區的時候
drop The user releases their mouse button, dropping the dragged item onto dropArea. 當使用者放開滑鼠並且被拖拉的項目是在drop區域的時候

出自:https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/

當我在進行這一段的時候,本來以為我只要監聽要被 drop 的地方而已,但有 drop 就一定會有 drag , drop 區域必須也要有監聽 drag 的所有事件才能有連帶反應。

而且必須在這些事件中加 e.preventDefault() 來阻止瀏覽器的預設行為,例如我現在要drag drop一個圖片上去,可是一直上不去是因為瀏覽器會自動幫我打開圖片。

為了防止瀏覽器的預設行為,都必須加上 e.preventDefault()

const dropZone = document.querySelector(".file-upload-zone");

dropZone.addEventListener("dragenter", function (e) {
     e.preventDefault();
});

dropZone.addEventListener("dragover", function (e) {
     e.preventDefault();
});

dropZone.addEventListener("dragleave", function (e) {
     e.preventDefault();
});

如果在各個監聽事件加上console.log('dragover') or 'dragenter',打開devTool就會看到很多 dragover 在那邊跑 XD 可以用這方式來確認是否有監聽到。

如何拿到檔案名稱?

這裡就完全跟 input 沒關係了,不過幸好 drag drop 也有提供可以拿到檔案的屬性,每個 drag event 都有 dataTransfer 的屬性可以使用。

不管加上哪一個 drag drop 監聽事件都有!不過要拿到檔案名稱的話還是要放在 drop 裏面才會被記錄下來。

console.log(e.dataTransfer)

可是打開files裡面居然沒有東西?後來看到這個:https://howtojs.io/empty-files-in-event-datatransfer-in-drop-event-in-javascript/

由於 Google 控制台的錯誤,您很可能會得到一個空數組。無需將 event.dataTransfer 記錄到控制台並展開它以查看 files 數組,您可以使用 event.dataTransfer.files 直接將 files 數組記錄到控制台,它會為您提供數組的長度以及 files 列表。

有可能會看到一個空陣列,可是其實可以直接用files打開:

console.log(e.dataTransfer.files)

拿到圖片的資訊了!所以不要相信那個空陣列,直接.files打開看看裡面的東西

實驗做完就可以來把這個名稱拿出來,放在要顯示檔案名稱的地方了:

dropZone.addEventListener("drop", function (e) {
    e.preventDefault();

    const fileName = e.dataTransfer.files[0].name;
    fileNameZone.textContent = fileName;
});

成果:

codepen:https://codepen.io/Jadddxx/pen/oNdaQKR?editors=0110

結論

  • input可以用 input.click() 來打開。
  • 雖然drag的東西不一定是原有的element,有可能是直接從資料夾拉出來的,但還是要使用dragenter, dragover, dragleave,因為這些都跟drop息息相關,必須要監聽到有drag的東西才能被drop當作目標放下來
  • drag drop必須使用 e.preventDefault() 來取消瀏覽器的預設行為,否則有可能檔案會自動被打開。
  • drag drop裏面的 e.dataTransfer 的陣列有可能是空的,但不要相信他,直接 e.dataTransfer.files 打開看看。
  • e.dataTransfer.files 要在drop的監聽事件裏面才能會記錄到。

參考資料:

MDN HTMLElement: drop event
MDN Using files from web applications
MDN HTMLElement.click()
MDN File drag and drop
Is call to preventDefault() really necessary on drop event?
JavaScript 中的 drop 事件中 event.dataTransfer 中的空文件
How To Make A Drag-and-Drop File Uploader With Vanilla JavaScript
What propagation I'm stopping in drag and drop?


上一篇
Day23 前端蛇行撞牆記 - addEventListener() 的參數(上)
下一篇
Day25 前端蛇行撞牆記 - addEventListener() 的參數(下)
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言