Le interfacce di trascinamento HTML consentono alle applicazioni web di accettare file trascinati e rilasciati su una pagina web. Durante un'operazione di trascinamento, gli elementi del file e della directory trascinati vengono associati rispettivamente alle voci dei file e delle directory. Esistono due modi per trascinare file nel browser: moderno e 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, scrivere nel file o nella directory. Tieni presente che l'interfaccia di trascinamento
DataTransferItem.kind
sarà
"file"
sia per i file sia per le directory, mentre per l'API File System Access
FileSystemHandle.kind
sarà
"file"
per i file e "directory"
per le directory.
Il classico
Utilizzo del metodo DataTransferItem.webkitGetAsEntry()
non standard
Il metodo DataTransferItem.webkitGetAsEntry()
restituisce FileSystemFileEntry
dell'elemento di dati di trascinamento se l'elemento è un file e FileSystemDirectoryEntry
se l'elemento è una directory. Anche se puoi leggere il file o la directory, non c'è modo di riscriverli. Questo metodo ha lo svantaggio di non essere nel canale standard, ma ha il vantaggio di supportare le directory.
Miglioramento progressivo
Lo snippet riportato di seguito utilizza il metodo DataTransferItem.getAsFileSystemHandle()
dell'API File System Access moderna, se supportato, quindi utilizza il metodo DataTransferItem.webkitGetAsEntry()
non standard e infine torna al metodo DataTransferItem.getAsFile()
classico. Assicurati di controllare il tipo di ogni handle
, poiché potrebbe essere una delle seguenti:
FileSystemDirectoryHandle
quando viene scelto il percorso di codice moderno.FileSystemDirectoryEntry
quando 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`;
}
}
});