Glisser-déposer des répertoires

Les interfaces de glisser-déposer HTML 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 aux entrées de fichier et de répertoire. Pour glisser-déposer des fichiers dans le navigateur, vous avez le choix entre deux méthodes: la méthode moderne et la méthode classique.

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 identifiants vous permettent de lire et éventuellement d'écrire dans le fichier ou le répertoire. Notez que la méthode DataTransferItem.kind de l'interface de glisser-déposer sera "file" pour les fichiers et les répertoires, tandis que la valeur FileSystemHandle.kind de l'API File System Access sera "file" pour les fichiers et "directory" pour les répertoires.

Navigateurs pris en charge

  • 86
  • 86
  • x
  • x

Source

Méthode classique

Utiliser la méthode DataTransferItem.webkitGetAsEntry() non standard

La méthode DataTransferItem.webkitGetAsEntry() renvoie la valeur FileSystemFileEntry de l'élément de données de déplacement si l'élément est un fichier et FileSystemDirectoryEntry s'il s'agit d'un répertoire. Bien que vous puissiez lire le fichier ou le répertoire, il n'y a aucun moyen d'y répondre. Cette méthode présente l'inconvénient qui n'est pas de type standard, mais a l'avantage de prendre en charge les répertoires.

Navigateurs pris en charge

  • 13
  • 14
  • 50
  • 11.1

Source

amélioration progressive

L'extrait ci-dessous utilise la méthode DataTransferItem.getAsFileSystemHandle() de l'API File System Access moderne lorsqu'elle est compatible, puis revient à la méthode DataTransferItem.webkitGetAsEntry() non standard et revient enfin à la méthode DataTransferItem.getAsFile() classique. Veillez à vérifier le type de chaque handle, car il peut s'agir de l'un des éléments suivants:

  • FileSystemDirectoryHandle lorsque le chemin de code moderne est choisi.
  • FileSystemDirectoryEntry lorsque le chemin de code non standard est choisi.

Tous les types ont une propriété name. Leur journalisation est donc acceptable et 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}`);
    }
  }
});

Complément d'informations

Démonstration

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`;
    }
  }
});