Verzeichnisse per Drag-and-drop hinzufügen

Thomas Steiner
Thomas Steiner

Mit den HTML-Drag-and-Drop-Oberflächen können Webanwendungen Drag-and-drop-Dateien auf einer Webseite akzeptieren. Während eines Drag-and-drop-Vorgangs werden per Drag-and-drop verschobene Datei- und Verzeichniselemente Datei- bzw. Verzeichniseinträgen zugeordnet. Es gibt zwei Möglichkeiten, Dateien per Drag-and-drop in den Browser zu ziehen: die moderne und die klassische.

Die moderne Art

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

Die Methode DataTransferItem.getAsFileSystemHandle() gibt ein Promise mit einem FileSystemFileHandle-Objekt zurück, wenn das gezogene Element eine Datei ist, und ein Promise mit einem FileSystemDirectoryHandle-Objekt, wenn das gezogene Element ein Verzeichnis ist. Mit diesen Handles können Sie die Datei oder das Verzeichnis lesen und optional zurückschreiben. Der DataTransferItem.kind der Drag-and-drop-Oberfläche ist "file" für Dateien und Verzeichnisse. FileSystemHandle.kind der File System Access API ist "file" für Dateien und "directory" für Verzeichnisse.

Unterstützte Browser

  • 86
  • 86
  • x
  • x

Quelle

Die klassische Art

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

Die Methode DataTransferItem.webkitGetAsEntry() gibt den FileSystemFileEntry des gezogenen 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 zwar lesen, aber nicht zurückschreiben. Diese Methode hat den Nachteil, dass sie nicht auf dem Standard-Track liegt, hat jedoch den Vorteil, dass sie Verzeichnisse unterstützt.

Unterstützte Browser

  • 13
  • 14
  • 50
  • 11.1

Quelle

Progressive Enhancement

Im folgenden Snippet wird die DataTransferItem.getAsFileSystemHandle()-Methode der modernen File System Access API verwendet, sofern sie unterstützt wird. Dann wird auf die nicht standardmäßige DataTransferItem.webkitGetAsEntry()-Methode zurückgesetzt und schließlich wird auf die klassische DataTransferItem.getAsFile()-Methode zurückgesetzt. Prüfen Sie den Typ jedes handle-Elements, da es sich um einen der folgenden Werte handeln kann:

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

Alle Typen haben ein name-Attribut. Die Protokollierung ist also kein Problem 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`;
    }
  }
});