Les interfaces HTML Drag and Drop permettent aux applications Web d'accepter les fichiers glissés-déposés sur une page Web. Lors d'une opération de glisser-déposer, les éléments de fichier et de répertoire glissés sont associés respectivement à des entrées de fichier et à des entrées de répertoire. Il existe deux façons de glisser-déposer des fichiers dans le navigateur : la méthode moderne et la méthode classique.
La méthode moderne
Utiliser la méthode DataTransferItem.getAsFileSystemHandle() de l'API File System Access
La méthode DataTransferItem.getAsFileSystemHandle() renvoie une promesse avec un objet FileSystemFileHandle si l'élément glissé est un fichier, et une promesse avec un objet FileSystemDirectoryHandle si l'élément glissé est un répertoire. Ces handles vous permettent de lire et, éventuellement, d'écrire dans le fichier ou le répertoire. Notez que
DataTransferItem.kind de l'interface Drag and Drop sera
"file" pour les fichiers et les répertoires, tandis que
FileSystemHandle.kind de l'API File System Access sera
"file" pour les fichiers et "directory" pour les répertoires.
La méthode classique
Utiliser la méthode classique DataTransferItem.getAsFile()
La méthode DataTransferItem.getAsFile() renvoie l'objet File de l'élément de données glissé. Si l'élément n'est pas un fichier, cette méthode renvoie null. Bien que vous puissiez lire le fichier, il n'existe aucun moyen d'y écrire. Cette méthode présente l'inconvénient de ne pas être compatible avec les répertoires.
Amélioration progressive
L'extrait ci-dessous utilise la méthode DataTransferItem.getAsFileSystemHandle() moderne de l'API File System Access lorsqu'elle est compatible, puis revient à la méthode non standard DataTransferItem.webkitGetAsEntry() et enfin à la méthode classique DataTransferItem.getAsFile(). Veillez à vérifier le type de chaque handle, car il peut s'agir de l'un des éléments suivants :
FileSystemFileHandlelorsque le chemin de code moderne est choisi.Filelorsque le chemin de code classique est choisi.
Tous les types ont une propriété name. Vous pouvez donc les enregistrer sans problème et cela fonctionnera toujours.
// 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}`);
}
}
});
Documentation complémentaire
Démo
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`;
}
}
});