Cách kéo và thả tệp

Giao diện Kéo và thả HTML cho phép các ứng dụng web chấp nhận tệp được kéo và thả trên trang web. Trong thao tác kéo và thả, các mục tệp và thư mục đã kéo sẽ được liên kết tương ứng với các mục nhập tệp và các mục nhập thư mục. Khi nói đến thao tác kéo và thả tệp vào trình duyệt, có hai cách thực hiện: cách hiện đại và cách cổ điển.

Phong cách hiện đại

Sử dụng phương thức DataTransferItem.getAsFileSystemHandle() của API Truy cập hệ thống tệp

Phương thức DataTransferItem.getAsFileSystemHandle() trả về một lời hứa với đối tượng FileSystemFileHandle nếu mục đã kéo là một tệp và một lời hứa với đối tượng FileSystemDirectoryHandle nếu mục được kéo là một thư mục. Các tên người dùng này cho phép bạn đọc và tuỳ ý ghi vào tệp hoặc thư mục. Xin lưu ý rằng DataTransferItem.kind của giao diện Kéo và thả sẽ là "file" cho cả tệp thư mục, trong khi FileSystemHandle.kind của API Truy cập hệ thống tệp sẽ là "file" cho tệp và "directory" cho thư mục.

Hỗ trợ trình duyệt

  • 86
  • 86
  • lần
  • lần

Nguồn

Cách cổ điển

Sử dụng phương thức DataTransferItem.getAsFile() cổ điển

Phương thức DataTransferItem.getAsFile() trả về đối tượng File của mục dữ liệu kéo. Nếu mục không phải là tệp, phương thức này sẽ trả về null. Mặc dù bạn có thể đọc tệp, nhưng không có cách nào để ghi lại vào tệp đó. Phương thức này có nhược điểm là không hỗ trợ các thư mục.

Hỗ trợ trình duyệt

  • 11
  • 12
  • 50
  • 5.1

Nguồn

Nâng cao dần dần

Đoạn mã dưới đây sử dụng phương thức DataTransferItem.getAsFileSystemHandle() của API Truy cập hệ thống tệp hiện đại khi được hỗ trợ, sau đó quay lại phương thức DataTransferItem.webkitGetAsEntry() không chuẩn và cuối cùng quay lại phương thức DataTransferItem.getAsFile() cổ điển. Hãy nhớ kiểm tra loại của từng handle, vì đó có thể là một trong hai loại:

  • FileSystemFileHandle khi đường dẫn mã hiện đại được chọn.
  • File khi đường dẫn mã cổ điển được chọn.

Mọi loại đều có thuộc tính name, vì vậy, việc ghi nhật ký thuộc tính này là bình thường và sẽ luôn hoạt động.

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

Tài liệu đọc thêm

Bản minh họa

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