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

토마스 슈타이너
토마스 슈타이너

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

현대적인 방식

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

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

브라우저 지원

  • 86
  • 86
  • x
  • x

소스

기존의 방식

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

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

브라우저 지원

  • 11
  • 12
  • 50
  • 5.1

소스

점진적 개선

아래 스니펫은 최신 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`;
    }
  }
});