Интерфейсы HTML Drag and Drop позволяют веб-приложениям принимать перетаскиваемые файлы на веб-странице. Во время операции перетаскивания перетаскиваемые элементы файлов и каталогов связаны с записями файлов и каталогов соответственно. Когда дело доходит до перетаскивания файлов в браузер, есть два способа сделать это: современный и классический.
Современный способ
Использование метода DataTransferItem.getAsFileSystemHandle()
API доступа к файловой системе
Метод DataTransferItem.getAsFileSystemHandle()
возвращает обещание с объектом FileSystemFileHandle
, если перетаскиваемый элемент является файлом, и обещание с объектом FileSystemDirectoryHandle
, если перетаскиваемый элемент является каталогом. Эти дескрипторы позволяют вам читать и, при необходимости, записывать обратно в файл или каталог. Обратите внимание, что DataTransferItem.kind
интерфейса перетаскивания будет "file"
как для файлов, так и для каталогов, тогда как FileSystemHandle.kind
API доступа к файловой системе будет "file"
для файлов и "directory"
для каталогов.
Классический способ
Использование нестандартного метода DataTransferItem.webkitGetAsEntry()
Метод DataTransferItem.webkitGetAsEntry()
возвращает FileSystemFileEntry
элемента данных перетаскивания, если элемент является файлом, и FileSystemDirectoryEntry
если элемент является каталогом. Хотя вы можете прочитать файл или каталог, невозможно выполнить обратную запись. Этот метод имеет тот недостаток, что не соответствует стандартам, но имеет то преимущество, что поддерживает каталоги.
Прогрессивное улучшение
В приведенном ниже фрагменте используется метод DataTransferItem.getAsFileSystemHandle()
современного 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`;
}
}
});