Verzeichnisse per Drag-and-drop hinzufügen

Mit den HTML-Drag-and-Drop-Schnittstellen können Webanwendungen per Drag‑and‑drop verschobene Dateien auf einer Webseite akzeptieren. Bei einem Drag‑and‑drop-Vorgang werden verschobene Datei- und Verzeichniselemente jeweils mit Dateieinträgen und Verzeichniseinträgen verknüpft. Es gibt zwei Möglichkeiten, Dateien per Drag‑and‑drop in den Browser zu ziehen: die moderne und die klassische Methode.

Die moderne Methode

Methode DataTransferItem.getAsFileSystemHandle() der File System Access API verwenden

Die Methode DataTransferItem.getAsFileSystemHandle() gibt ein Promise mit einem FileSystemFileHandle-Objekt zurück, wenn das verschobene Element eine Datei ist, und ein Promise mit einem FileSystemDirectoryHandle-Objekt, wenn das verschobene Element ein Verzeichnis ist. Mit diesen Handles können Sie die Datei oder das Verzeichnis lesen und optional wieder in sie schreiben. Beachten Sie, dass DataTransferItem.kind der Drag‑and‑drop-Schnittstelle "file" für Dateien und Verzeichnisse ist, während FileSystemHandle.kind der File System Access API für Dateien "file" und für Verzeichnisse "directory" ist.

Browser Support

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

Source

Die klassische Methode

Nicht standardmäßige Methode DataTransferItem.webkitGetAsEntry() verwenden

Die Methode DataTransferItem.webkitGetAsEntry() gibt das FileSystemFileEntry des Drag‑and‑drop-Datenelements zurück, wenn das Element eine Datei ist, und FileSystemDirectoryEntry, wenn das Element ein Verzeichnis ist. Sie können die Datei oder das Verzeichnis lesen, aber nicht in sie schreiben. Diese Methode hat den Nachteil, dass sie nicht standardisiert ist, aber den Vorteil, dass sie Verzeichnisse unterstützt.

Browser Support

  • Chrome: 13.
  • Edge: 14.
  • Firefox: 50.
  • Safari: 11.1.

Source

Progressive Enhancement

Im folgenden Snippet wird die moderne Methode DataTransferItem.getAsFileSystemHandle() der File System Access API verwendet, wenn sie unterstützt wird. Andernfalls wird auf die nicht standardmäßige Methode DataTransferItem.webkitGetAsEntry() zurückgegriffen und schließlich auf die klassische Methode DataTransferItem.getAsFile(). Prüfen Sie den Typ jedes handle, da er einer der folgenden sein kann:

  • FileSystemDirectoryHandle, wenn der moderne Codepfad ausgewählt ist.
  • FileSystemDirectoryEntry, wenn der nicht standardmäßige Codepfad ausgewählt ist.

Alle Typen haben eine name-Eigenschaft. Das Protokollieren ist also in Ordnung und funktioniert immer.

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

Weitere Informationen

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