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 classico DataTransferItem.getAsFile()
Il metodo DataTransferItem.getAsFile() restituisce l'oggetto File dell'elemento di dati di trascinamento. Se l'elemento non è un file, questo metodo restituisce null. Anche se puoi leggere il file, non è possibile scriverci di nuovo. Questo metodo presenta lo svantaggio di non 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:
FileSystemFileHandlequando viene scelto il percorso del codice moderno.Filequando viene scelto il percorso del codice classico.
Tutti i tipi hanno una proprietà name, quindi la registrazione è corretta e funzionerà sempre.
// Run feature detection.
const supportsFileSystemAccessAPI =
'getAsFileSystemHandle' 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();
// 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 `File`.
: item.getAsFile(),
);
// Loop over the array of promises.
for await (const handle of fileHandlesPromises) {
// This is where we can actually exclusively act on the files.
if (handle.kind === 'file' || handle.isFile) {
console.log(`File: ${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 files</title>
</head>
<body>
<main>
<h1>How to drag and drop files</h1>
<p>Drag and drop one or multiple files 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`;
}
}
});