Jak przeciągać i upuszczać katalogi

Interfejsy HTML typu „przeciągnij i upuść” umożliwiają aplikacjom internetowym akceptowanie przeciągniętych i upuszczonych plików na stronie internetowej. Podczas operacji przeciągania i upuszczania przeciągane elementy pliku i katalogu są powiązane odpowiednio z wpisami pliku i katalogu. Można przeciągać i upuszczać pliki do przeglądarki na dwa sposoby: nowoczesny i klasyczny.

Nowoczesny sposób

Za pomocą metody DataTransferItem.getAsFileSystemHandle() interfejsu File System Access API

Metoda DataTransferItem.getAsFileSystemHandle() zwraca obietnicę z obiektem FileSystemFileHandle, jeśli przeciągnięty element jest plikiem, oraz obietnicę z obiektem FileSystemDirectoryHandle, jeśli przeciągnięty element jest katalogiem. Dzięki nim możesz odczytywać pliki lub katalogi, a także opcjonalnie zapisywać w nim dane. Pamiętaj, że interfejs przeciągania i upuszczania DataTransferItem.kind ma wartość "file" zarówno dla plików, jak i katalogów, natomiast interfejs File System Access API będzie mieć wartość "file" dla plików i "directory" dla katalogów.FileSystemHandle.kind

Obsługa przeglądarek

  • 86
  • 86
  • x
  • x

Źródło

Klasyczny sposób

Przy użyciu niestandardowej metody DataTransferItem.webkitGetAsEntry()

Metoda DataTransferItem.webkitGetAsEntry() zwraca FileSystemFileEntry elementu danych przeciągania, jeśli element jest plikiem, oraz FileSystemDirectoryEntry, jeśli element jest katalogiem. Możesz odczytać plik lub katalog, ale nie możesz do nich odpisać. Ta metoda ma wadę, która nie jest uznawana za standard. Zaletą jest to, że obsługuje katalogi.

Obsługa przeglądarek

  • 13
  • 14
  • 50
  • 11.1

Źródło

Stopniowe ulepszanie

Poniższy fragment kodu korzysta z nowoczesnej metody DataTransferItem.getAsFileSystemHandle() interfejsu File System Access API (gdy jest obsługiwana), a następnie przełącza się na niestandardową metodę DataTransferItem.webkitGetAsEntry() i w końcu wraca do klasycznej metody DataTransferItem.getAsFile(). Sprawdź typ każdego atrybutu handle, ponieważ może to być dowolny z tych elementów:

  • FileSystemDirectoryHandle, gdy wybrana jest nowoczesna ścieżka kodu.
  • FileSystemDirectoryEntry, gdy wybrana jest niestandardowa ścieżka kodu.

Wszystkie typy mają właściwość name, więc rejestrowanie jest prawidłowe i zawsze będzie działać.

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

Więcej informacji

Prezentacja

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