파일을 드래그 앤 드롭하는 방법

HTML 드래그 앤 드롭 인터페이스를 사용하면 웹 애플리케이션이 웹페이지에서 드래그 앤 드롭된 파일을 수락할 수 있습니다. 드래그 앤 드롭 작업 중에 드래그된 파일 및 디렉터리 항목이 각각 파일 항목 및 디렉터리 항목과 연결됩니다. 파일을 브라우저로 드래그 앤 드롭하는 방법에는 최신 방식과 기존 방식의 두 가지가 있습니다.

최신 방식

File System Access API의 DataTransferItem.getAsFileSystemHandle() 메서드 사용

DataTransferItem.getAsFileSystemHandle() 메서드는 드래그된 항목이 파일인 경우 FileSystemFileHandle 객체가 포함된 프로미스를 반환하고 드래그된 항목이 디렉터리인 경우 FileSystemDirectoryHandle 객체가 포함된 프로미스를 반환합니다. 이러한 핸들을 사용하면 파일을 읽고 선택적으로 파일 또는 디렉터리에 다시 쓸 수 있습니다. 드래그 앤 드롭 인터페이스의 DataTransferItem.kind는 파일 디렉터리 모두에 대해 "file"인 반면 File System Access API의 FileSystemHandle.kind는 파일의 경우 "file"이고 디렉터리의 경우 "directory"입니다.

Browser Support

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

Source

기존 방식

기존 DataTransferItem.getAsFile() 메서드 사용

DataTransferItem.getAsFile() 메서드는 드래그 데이터 항목의 File 객체를 반환합니다. 항목이 파일이 아닌 경우 이 메서드는 null을 반환합니다. 파일을 읽을 수는 있지만 다시 쓰는 방법은 없습니다. 이 메서드는 디렉터리를 지원하지 않는다는 단점이 있습니다.

Browser Support

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

Source

점진적 개선

아래 스니펫은 지원되는 경우 최신 File System Access API의 DataTransferItem.getAsFileSystemHandle() 메서드를 사용한 다음 비표준 DataTransferItem.webkitGetAsEntry() 메서드로 대체하고 마지막으로 기존 DataTransferItem.getAsFile() 메서드로 대체합니다. 각 handle의 유형을 확인하세요. 다음 중 하나일 수 있습니다.

  • 최신 코드 경로가 선택된 경우 FileSystemFileHandle
  • 기존 코드 경로가 선택된 경우 File

모든 유형에는 name 속성이 있으므로 로깅해도 괜찮으며 항상 작동합니다.

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

추가 자료

데모

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