อินเทอร์เฟซการลากและวางแบบ HTML ช่วยให้เว็บแอปพลิเคชันยอมรับไฟล์ที่ลากและวางบนหน้าเว็บได้ ระหว่างการดำเนินการลากและวาง ไฟล์ที่ลากและรายการไดเรกทอรีจะเชื่อมโยงกับรายการไฟล์และรายการไดเรกทอรีตามลำดับ เมื่อพูดถึงการลากและวางไฟล์ลงในเบราว์เซอร์ มี 2 วิธีในการดำเนินการ ได้แก่ แบบสมัยใหม่และคลาสสิก
วิธีสมัยใหม่
การใช้เมธอด DataTransferItem.getAsFileSystemHandle()
ของ File System Access API
เมธอด DataTransferItem.getAsFileSystemHandle()
จะส่งกลับคำสัญญาพร้อมด้วยออบเจ็กต์ FileSystemFileHandle
หากรายการที่ลากเป็นไฟล์ และคำสัญญาที่มีออบเจ็กต์ FileSystemDirectoryHandle
หากรายการที่ลากเป็นไดเรกทอรี แฮนเดิลเหล่านี้ให้คุณอ่านและเลือกเขียนกลับไปยังไฟล์หรือไดเรกทอรีได้ โปรดทราบว่า DataTransferItem.kind
ของอินเทอร์เฟซแบบลากและวางจะเป็น "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`;
}
}
});