Le interfacce di trascinamento HTML consentono alle applicazioni web di accettare i file trascinati e rilasciati su una pagina web. Durante un'operazione di trascinamento, gli elementi di file e directory trascinati vengono associati rispettivamente alle voci di file e alle voci di directory. Quando si tratta di trascinare e rilasciare file nel browser, esistono due modi per farlo: il modo moderno e quello classico.
Il modo moderno
Utilizzo del metodo DataTransferItem.getAsFileSystemHandle() dell'API File System Access
Il metodo DataTransferItem.getAsFileSystemHandle() restituisce una promessa con un oggetto FileSystemFileHandle se l'elemento trascinato è un file e una promessa con un oggetto FileSystemDirectoryHandle se l'elemento trascinato è una directory. Questi handle consentono di leggere e, facoltativamente, di scrivere nuovamente nel file o nella directory. Tieni presente che
DataTransferItem.kind dell'interfaccia di trascinamento sarà
"file" sia per i file che per le directory, mentre
FileSystemHandle.kind dell'API File System Access sarà
"file" per i file e "directory" per le directory.
Il modo classico
Utilizzo del metodo non standard DataTransferItem.webkitGetAsEntry()
Il metodo DataTransferItem.webkitGetAsEntry() restituisce FileSystemFileEntry dell'elemento di dati di trascinamento se l'elemento è un file e FileSystemDirectoryEntry se l'elemento è una directory. Sebbene sia possibile leggere il file o la directory, non è possibile scriverli nuovamente. Questo metodo presenta lo svantaggio di non essere conforme agli standard, ma ha il vantaggio di supportare le directory.
Potenziamento progressivo
Lo snippet riportato di seguito utilizza il metodo DataTransferItem.getAsFileSystemHandle() dell'API File System Access moderna quando è supportato, quindi esegue il fallback al metodo non standard DataTransferItem.webkitGetAsEntry() e infine al metodo classico DataTransferItem.getAsFile(). Assicurati di controllare il tipo di ogni handle, poiché potrebbe essere uno dei seguenti:
FileSystemDirectoryHandlequando viene scelto il percorso del codice moderno.FileSystemDirectoryEntryquando viene scelto il percorso del codice non standard.
Tutti i tipi hanno una proprietà name, quindi la registrazione è corretta e funzionerà sempre.
// 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}`);
}
}
});
Per approfondire
Demo
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`;
}
}
});