อินเทอร์เฟซ HTML Drag and Drop ช่วยให้เว็บแอปพลิเคชันยอมรับ ไฟล์ที่ลากและวาง บนหน้าเว็บได้ ระหว่างการดำเนินการลากและวาง ระบบจะเชื่อมโยงรายการไฟล์และไดเรกทอรีที่ลากกับรายการไฟล์และรายการไดเรกทอรีตามลำดับ เมื่อพูดถึงการลากและวางไฟล์ลงในเบราว์เซอร์ คุณทำได้ 2 วิธี ได้แก่ วิธีสมัยใหม่และวิธีคลาสสิก
วิธีสมัยใหม่
การใช้วิธี DataTransferItem.getAsFileSystemHandle() ของ File System Access API
วิธี DataTransferItem.getAsFileSystemHandle() จะแสดงผล Promise ที่มีออบเจ็กต์ FileSystemFileHandle หากรายการที่ลากเป็นไฟล์ และแสดงผล Promise ที่มีออบเจ็กต์ FileSystemDirectoryHandle หากรายการที่ลากเป็นไดเรกทอรี แฮนเดิลเหล่านี้ช่วยให้คุณอ่านและเลือกที่จะเขียนกลับไปยังไฟล์หรือไดเรกทอรีได้ โปรดทราบว่า
DataTransferItem.kind ของอินเทอร์เฟซ Drag and Drop จะเป็น
"file" สำหรับทั้งไฟล์ และ ไดเรกทอรี ในขณะที่
FileSystemHandle.kind ของ File System Access API จะ
เป็น "file" สำหรับไฟล์และ "directory" สำหรับไดเรกทอรี
วิธีคลาสสิก
การใช้วิธี DataTransferItem.webkitGetAsEntry() ที่ไม่ใช่มาตรฐาน
วิธี DataTransferItem.webkitGetAsEntry() จะแสดงผล FileSystemFileEntry ของรายการข้อมูลการลากหากรายการนั้นเป็นไฟล์ และแสดงผล FileSystemDirectoryEntry หากรายการนั้นเป็นไดเรกทอรี แม้ว่าคุณจะอ่านไฟล์หรือไดเรกทอรีได้ แต่ก็ไม่มีวิธีเขียนกลับไปยังไฟล์หรือไดเรกทอรีเหล่านั้น วิธีนี้มีข้อเสียคือไม่ได้อยู่ในเส้นทางมาตรฐาน แต่มีข้อดีคือรองรับไดเรกทอรี
การเพิ่มประสิทธิภาพแบบต่อเนื่อง
ข้อมูลโค้ดด้านล่างใช้วิธี DataTransferItem.getAsFileSystemHandle() ของ File System Access API สมัยใหม่เมื่อรองรับ จากนั้นจะกลับไปใช้วิธี DataTransferItem.webkitGetAsEntry() ที่ไม่ใช่มาตรฐาน และสุดท้ายจะกลับไปใช้วิธี DataTransferItem.getAsFile() แบบคลาสสิก โปรดตรวจสอบประเภทของ handle แต่ละรายการ เนื่องจากอาจเป็นประเภทใดประเภทหนึ่งต่อไปนี้
FileSystemDirectoryHandleเมื่อเลือกเส้นทางโค้ดสมัยใหม่FileSystemDirectoryEntryเมื่อเลือกเส้นทางโค้ดที่ไม่ใช่มาตรฐาน
ประเภททั้งหมดมีพร็อพเพอร์ตี้ name ดังนั้นการบันทึกจึงทำได้และจะทำงานได้เสมอ
// Run feature detection.
const supportsFileSystemAccessAPI =
'getAsFileSystemHandle' in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
'webkitGetAsEntry' in DataTransferItem.prototype;
// This is the drag and drop zone.
const elem = document.querySelector('main');
// Prevent navigation.
elem.addEventListener('dragover', (e) => {
e.preventDefault();
});
// Visually highlight the drop zone.
elem.addEventListener('dragenter', (e) => {
elem.style.outline = 'solid red 1px';
});
// Visually unhighlight the drop zone.
elem.addEventListener('dragleave', (e) => {
elem.style.outline = '';
});
// This is where the drop is handled.
elem.addEventListener('drop', async (e) => {
// Prevent navigation.
e.preventDefault();
if (!supportsFileSystemAccessAPI && !supportsWebkitGetAsEntry) {
// Cannot handle directories.
return;
}
// Unhighlight the drop zone.
elem.style.outline = '';
// Prepare an array of promises…
const fileHandlesPromises = [...e.dataTransfer.items]
// …by including only files (where file misleadingly means actual file _or_
// directory)…
.filter((item) => item.kind === 'file')
// …and, depending on previous feature detection…
.map((item) =>
supportsFileSystemAccessAPI
// …either get a modern `FileSystemHandle`…
? item.getAsFileSystemHandle()
// …or a classic `FileSystemFileEntry`.
: item.webkitGetAsEntry(),
);
// Loop over the array of promises.
for await (const handle of fileHandlesPromises) {
// This is where we can actually exclusively act on the directories.
if (handle.kind === 'directory' || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
}
}
});
อ่านเพิ่มเติม
สาธิต
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to drag and drop directories</title>
</head>
<body>
<main>
<h1>How to drag and drop directories</h1>
<p>Drag and drop one or multiple files or directories onto the page.</p>
<pre></pre>
</main>
</body>
</html>CSS
:root {
color-scheme: dark light;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
img,
video {
height: auto;
max-width: 100%;
}
main {
flex-grow: 1;
}
footer {
margin-top: 1rem;
border-top: solid CanvasText 1px;
font-size: 0.8rem;
}
JS
const supportsFileSystemAccessAPI =
"getAsFileSystemHandle" in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
"webkitGetAsEntry" in DataTransferItem.prototype;
const elem = document.querySelector("main");
const debug = document.querySelector("pre");
elem.addEventListener("dragover", (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener("dragenter", (e) => {
elem.style.outline = "solid red 1px";
});
elem.addEventListener("dragleave", (e) => {
elem.style.outline = "";
});
elem.addEventListener("drop", async (e) => {
e.preventDefault();
elem.style.outline = "";
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === "file")
.map((item) =>
supportsFileSystemAccessAPI
? item.getAsFileSystemHandle()
: supportsWebkitGetAsEntry
? item.webkitGetAsEntry()
: item.getAsFile()
);
for await (const handle of fileHandlesPromises) {
if (handle.kind === "directory" || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
debug.textContent += `Directory: ${handle.name}\n`;
} else {
console.log(`File: ${handle.name}`);
debug.textContent += `File: ${handle.name}\n`;
}
}
});