Jak przeciągać i upuszczać pliki

Interfejsy HTML Drag and Drop umożliwiają aplikacjom internetowym akceptowanie przeciąganych i upuszczanych plików na stronie internetowej. Podczas operacji przeciągania i upuszczania przeciągane pliki i katalogi są powiązane odpowiednio z wpisami plików i wpisami katalogów. Jeśli chodzi o przeciąganie i upuszczanie plików w przeglądarce, można to zrobić na 2 sposoby: nowoczesny i klasyczny.

Nowoczesny sposób

Używanie metody DataTransferItem.getAsFileSystemHandle() interfejsu File System Access API

Metoda DataTransferItem.getAsFileSystemHandle() zwraca obietnicę z obiektem FileSystemFileHandle, jeśli przeciągany element jest plikiem, oraz obietnicę z obiektem FileSystemDirectoryHandle, jeśli przeciągany element jest katalogiem. Te uchwyty umożliwiają odczyt i opcjonalnie zapisywanie z powrotem do pliku lub katalogu. Pamiętaj, że w przypadku plików i katalogów właściwość interfejsu Drag and Drop DataTransferItem.kind będzie mieć wartość "file", natomiast w przypadku plików właściwość interfejsu File System Access API FileSystemHandle.kind będzie mieć wartość "file", a w przypadku katalogów – "directory".

Browser Support

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

Source

Klasyczny sposób

Używanie klasycznej metody DataTransferItem.getAsFile()

Metoda DataTransferItem.getAsFile() zwraca obiekt File elementu danych przeciągania. Jeśli element nie jest plikiem, ta metoda zwraca wartość null. Możesz odczytać plik, ale nie możesz go zapisać. Ta metoda ma tę wadę, że nie obsługuje katalogów.

Browser Support

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

Source

Progresywne ulepszanie

Poniższy fragment kodu używa nowoczesnej metody DataTransferItem.getAsFileSystemHandle() interfejsu File System Access API, jeśli jest obsługiwana, a następnie przechodzi do niestandardowej metody DataTransferItem.webkitGetAsEntry() i na końcu do klasycznej metody DataTransferItem.getAsFile(). Sprawdź typ każdego handle, ponieważ może to być:

  • FileSystemFileHandle, gdy wybrana jest nowoczesna ścieżka kodu.
  • File, gdy wybrana jest klasyczna ścieżka kodu.

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

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

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