Les interfaces HTML Drag and Drop permettent aux applications Web d'accepter les fichiers glissés et 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. Pour glisser-déposer des fichiers dans le navigateur, vous pouvez procéder de deux manières : 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 le fichier ou le répertoire, et éventuellement d'y écrire. 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 non standard DataTransferItem.webkitGetAsEntry()
La méthode DataTransferItem.webkitGetAsEntry() renvoie le FileSystemFileEntry de l'élément de données glissé si l'élément est un fichier, et FileSystemDirectoryEntry si l'élément est un répertoire. Vous pouvez lire le fichier ou le répertoire, mais vous ne pouvez pas y écrire. Cette méthode présente l'inconvénient de ne pas être conforme aux normes, mais elle a l'avantage de prendre en charge 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 :
FileSystemDirectoryHandlelorsque le chemin de code moderne est choisi.FileSystemDirectoryEntrylorsque le chemin de code non standard est choisi.
Tous les types ont une propriété name. Vous pouvez donc la consigner sans problème, et elle fonctionnera toujours.
// 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}`);
}
}
});
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 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`;
}
}
});