Glisser-déposer des fichiers

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.

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: not supported.
  • Safari: not supported.

Source

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.

Browser Support

  • Chrome: 11.
  • Edge: 12.
  • Firefox: 50.
  • Safari: 5.1.

Source

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 :

  • FileSystemFileHandle lorsque le chemin de code moderne est choisi.
  • File lorsque 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`;
    }
  }
});