檔案拖放
正如著陸頁上所提到的,拖放 API 同時模擬了三種用例:在頁面內拖動元素,將資料拖出頁面,以及將資料拖入頁面。本教程演示了第三種用例:將資料拖入頁面。我們將實現一個基本的放置區域,允許使用者從作業系統的檔案瀏覽器中拖放圖片檔案,並在頁面上顯示它們。對於不能或不想使用拖放的使用者,我們也提供了透過<input> 元素進行檔案選擇的替代功能。
頁面基本佈局
因為我們希望允許正常的<input> 檔案選擇,所以讓放置區域由<input> 元素支援是有意義的,這樣我們就可以同時拖放到它上面並點選它。我們利用了一個常見的技巧,就是讓<input> 不可見,並使用與之關聯的<label> 來與使用者互動,因為<label> 元素更容易進行樣式設定。我們還添加了用於預覽拖放圖片的元素。
<label id="drop-zone">
Drop images here, or click to upload.
<input type="file" id="file-input" multiple accept="image/*" />
</label>
<ul id="preview"></ul>
<button id="clear-btn">Clear</button>
我們為 label 元素設定樣式,以直觀地指示該元素是一個放置區域,並隱藏檔案輸入框。
body {
font-family: "Arial", sans-serif;
}
#drop-zone {
display: flex;
align-items: center;
justify-content: center;
width: 500px;
max-width: 100%;
height: 200px;
padding: 1em;
border: 1px solid #cccccc;
border-radius: 4px;
color: slategray;
cursor: pointer;
}
#file-input {
display: none;
}
#preview {
width: 500px;
max-width: 100%;
display: flex;
flex-direction: column;
gap: 0.5em;
list-style: none;
padding: 0;
}
#preview li {
display: flex;
align-items: center;
gap: 0.5em;
margin: 0;
width: 100%;
height: 100px;
}
#preview img {
width: 100px;
height: 100px;
object-fit: cover;
}
由於我們使用了<label> 和<input> 元素,因此無需額外的 JavaScript 來實現檔案選擇的使用者體驗。現在我們專注於檔案放置和隨後對放置檔案的處理。
宣告放置目標
我們的放置目標是<label> 元素。作為目標元素,它會監聽drop 事件來處理放置的檔案。
const dropZone = document.getElementById("drop-zone");
dropZone.addEventListener("drop", dropHandler);
對於檔案放置,瀏覽器可能會預設處理它們(例如開啟或下載檔案),即使檔案沒有被拖放到有效的放置目標。為了阻止這種行為,我們還需要監聽window 上的drop 事件並取消它。我們小心地只處理有檔案被拖動的情況;如果拖動的是其他東西,例如連結,我們仍然會使用預設行為。如果拖動的項是非圖片檔案,我們仍然會處理該事件,但會向用戶提供反饋,表明不允許該檔案。
window.addEventListener("drop", (e) => {
if ([...e.dataTransfer.items].some((item) => item.kind === "file")) {
e.preventDefault();
}
});
為了使drop 事件觸發,該元素還必須取消dragover 事件。因為我們監聽的是window 上的drop 事件,所以我們也需要取消整個window 的dragover 事件。如果檔案不是圖片或沒有被拖動到正確的位置,我們還將DataTransfer.dropEffect 設定為none。
dropZone.addEventListener("dragover", (e) => {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === "file",
);
if (fileItems.length > 0) {
e.preventDefault();
if (fileItems.some((item) => item.type.startsWith("image/"))) {
e.dataTransfer.dropEffect = "copy";
} else {
e.dataTransfer.dropEffect = "none";
}
}
});
window.addEventListener("dragover", (e) => {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === "file",
);
if (fileItems.length > 0) {
e.preventDefault();
if (!dropZone.contains(e.target)) {
e.dataTransfer.dropEffect = "none";
}
}
});
處理放置
現在我們透過使用getAsFile() 方法來訪問每個檔案來實現dropHandler。然後,您的應用程式可以使用File API 決定如何處理該檔案。這裡我們只是在頁面上顯示它們;在實際應用中,您可能還希望最終將它們上傳到伺服器。
const preview = document.getElementById("preview");
function displayImages(files) {
for (const file of files) {
if (file.type.startsWith("image/")) {
const li = document.createElement("li");
const img = document.createElement("img");
img.src = URL.createObjectURL(file);
img.alt = file.name;
li.appendChild(img);
li.appendChild(document.createTextNode(file.name));
preview.appendChild(li);
}
}
}
function dropHandler(ev) {
ev.preventDefault();
const files = [...ev.dataTransfer.items]
.map((item) => item.getAsFile())
.filter((file) => file);
displayImages(files);
}
將相同行為新增到輸入框
以上是拖放的整個資料流;現在我們需要將displayImages() 函式也連線到檔案輸入框。
const fileInput = document.getElementById("file-input");
fileInput.addEventListener("change", (e) => {
displayImages(e.target.files);
});
清除按鈕
最後,我們添加了一種清除預覽區域的方法。我們使用URL.revokeObjectURL() 來釋放影像物件使用的記憶體。
const clearBtn = document.getElementById("clear-btn");
clearBtn.addEventListener("click", () => {
for (const img of preview.querySelectorAll("img")) {
URL.revokeObjectURL(img.src);
}
preview.textContent = "";
});