Giao diện Kéo và thả HTML cho phép các ứng dụng web chấp nhận tệp được kéo và thả trên trang web. Trong thao tác kéo và thả, các mục tệp và thư mục đã kéo sẽ được liên kết tương ứng với các mục nhập tệp và các mục nhập thư mục. Khi nói đến thao tác kéo và thả tệp vào trình duyệt, có hai cách thực hiện: cách hiện đại và cách cổ điển.
Phong cách hiện đại
Sử dụng phương thức DataTransferItem.getAsFileSystemHandle()
của API Truy cập hệ thống tệp
Phương thức DataTransferItem.getAsFileSystemHandle()
trả về một lời hứa với đối tượng FileSystemFileHandle
nếu mục đã kéo là một tệp và một lời hứa với đối tượng FileSystemDirectoryHandle
nếu mục được kéo là một thư mục. Các tên người dùng này cho phép bạn đọc và tuỳ ý ghi vào tệp hoặc thư mục. Xin lưu ý rằng DataTransferItem.kind
của giao diện Kéo và thả sẽ là "file"
cho cả tệp và thư mục, trong khi FileSystemHandle.kind
của API Truy cập hệ thống tệp sẽ là "file"
cho tệp và "directory"
cho thư mục.
Cách cổ điển
Sử dụng phương thức DataTransferItem.webkitGetAsEntry()
không chuẩn
Phương thức DataTransferItem.webkitGetAsEntry()
sẽ trả về FileSystemFileEntry
của mục dữ liệu kéo nếu mục đó là tệp và FileSystemDirectoryEntry
nếu mục đó là thư mục. Mặc dù bạn có thể đọc tệp hoặc thư mục, nhưng không có cách nào để ghi lại vào tệp hoặc thư mục đó. Phương thức này có nhược điểm là không có trên kênh tiêu chuẩn, nhưng có ưu điểm là hỗ trợ các thư mục.
Nâng cao dần dần
Đoạn mã dưới đây sử dụng phương thức DataTransferItem.getAsFileSystemHandle()
của API Truy cập hệ thống tệp hiện đại khi được hỗ trợ, sau đó quay lại phương thức DataTransferItem.webkitGetAsEntry()
không chuẩn và cuối cùng quay lại phương thức DataTransferItem.getAsFile()
cổ điển. Hãy nhớ kiểm tra loại của từng handle
, vì đó có thể là một trong hai loại:
FileSystemDirectoryHandle
khi đường dẫn mã hiện đại được chọn.FileSystemDirectoryEntry
khi đường dẫn mã không chuẩn được chọn.
Mọi loại đều có thuộc tính name
, vì vậy, việc ghi nhật ký thuộc tính này là bình thường và sẽ luôn hoạt động.
// 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}`);
}
}
});
Tài liệu đọc thêm
Bản minh họa
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`;
}
}
});