Cách mở một hoặc nhiều tệp

Xử lý tệp là một trong những thao tác phổ biến nhất đối với ứng dụng trên web. Thông thường, người dùng phải tải tệp lên, thực hiện một số thay đổi đối với tệp, sau đó tải tệp xuống lại, dẫn đến một bản sao trong thư mục Tệp đã tải xuống. Nhờ API Truy cập hệ thống tệp, người dùng hiện có thể trực tiếp mở tệp, thực hiện sửa đổi và lưu lại các thay đổi vào tệp gốc.

Phong cách hiện đại

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

Để mở một tệp, hãy gọi showOpenFilePicker(). Phương thức này sẽ trả về lời hứa kèm theo một mảng gồm các tệp đã chọn. Nếu cần nhiều tệp, bạn có thể chuyển { multiple: true, } vào phương thức này.

Hỗ trợ trình duyệt

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

Nguồn

Cách cổ điển

Sử dụng phần tử <input type="file">

Phần tử <input type="file"> trên trang cho phép người dùng nhấp vào phần tử đó và mở một hoặc nhiều tệp. Thủ thuật hiện bao gồm chèn phần tử không hiển thị vào một trang bằng JavaScript và nhấp vào phần tử đó theo phương thức lập trình.

Hỗ trợ trình duyệt

  • 1
  • 12
  • 1
  • 1

Nguồn

Nâng cao dần dần

Phương thức bên dưới sử dụng API Truy cập hệ thống tệp khi được hỗ trợ, ngoài ra sẽ quay lại phương pháp cũ. Trong cả hai trường hợp, hàm trả về một mảng tệp, nhưng trong trường hợp API Truy cập hệ thống tệp được hỗ trợ, mỗi đối tượng tệp cũng có một FileSystemFileHandle được lưu trữ trong thuộc tính handle, vì vậy, bạn có thể chuyển đổi tuần tự tên người dùng vào ổ đĩa.

const openFileOrFiles = async (multiple = false) => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showOpenFilePicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();
  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let fileOrFiles = undefined;
    try {
      // Show the file picker, optionally allowing multiple files.
      const handles = await showOpenFilePicker({ multiple });
      // Only one file is requested.
      if (!multiple) {
        // Add the `FileSystemFileHandle` as `.handle`.
        fileOrFiles = await handles[0].getFile();
        fileOrFiles.handle = handles[0];
      } else {
        fileOrFiles = await Promise.all(
          handles.map(async (handle) => {
            const file = await handle.getFile();
            // Add the `FileSystemFileHandle` as `.handle`.
            file.handle = handle;
            return file;
          })
        );
      }
    } catch (err) {
      // Fail silently if the user has simply canceled the dialog.
      if (err.name !== 'AbortError') {
        console.error(err.name, err.message);
      }
    }
    return fileOrFiles;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    // Append a new `<input type="file" multiple? />` and hide it.
    const input = document.createElement('input');
    input.style.display = 'none';
    input.type = 'file';
    document.body.append(input);
    if (multiple) {
      input.multiple = true;
    }
    // The `change` event fires when the user interacts with the dialog.
    input.addEventListener('change', () => {
      // Remove the `<input type="file" multiple? />` again from the DOM.
      input.remove();
      // If no files were selected, return.
      if (!input.files) {
        return;
      }
      // Return all files or just one file.
      resolve(multiple ? Array.from(input.files) : input.files[0]);
    });
    // Show the picker.
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

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" />
    <link
      rel="icon"
      href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
    />
    <title>How to open one or multiple files</title>
  </head>
  <body>
    <h1>How to open one or multiple files</h1>
    <button type="button">Open file</button>
    <button class="multiple" type="button">Open files</button>
    <pre></pre>
  </body>
</html>

CSS


        :root {
  color-scheme: dark light;
}

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
  font-family: system-ui, sans-serif;
}

button {
  margin: 1rem;
}
        

JS


        const button = document.querySelector('button');
const buttonMultiple = document.querySelector('button.multiple');
const pre = document.querySelector('pre');

const openFileOrFiles = async (multiple = false) => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showOpenFilePicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();

  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let fileOrFiles = undefined;
    try {
      // Show the file picker, optionally allowing multiple files.
      fileOrFiles = await showOpenFilePicker({ multiple });
      if (!multiple) {
        // Only one file is requested.
        fileOrFiles = fileOrFiles[0];
      }
    } catch (err) {
      // Fail silently if the user has simply canceled the dialog.
      if (err.name !== 'AbortError') {
        console.error(err.name, err.message);
      }
    }
    return fileOrFiles;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    // Append a new `` and hide it.
    const input = document.createElement('input');
    input.style.display = 'none';
    input.type = 'file';
    document.body.append(input);
    if (multiple) {
      input.multiple = true;
    }
    // The `change` event fires when the user interacts with the dialog.
    input.addEventListener('change', () => {
      // Remove the `` again from the DOM.
      input.remove();
      // If no files were selected, return.
      if (!input.files) {
        return;
      }
      // Return all files or just one file.
      resolve(multiple ? input.files : input.files[0]);
    });
    // Show the picker.
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

button.addEventListener('click', async () => {
  const file = await openFileOrFiles();
  if (!file) {
    return;
  }
  pre.textContent += `${file.name}\n`;
});

buttonMultiple.addEventListener('click', async () => {
  const files = await openFileOrFiles(true);
  if (!files) {
    return;
  }
  Array.from(files).forEach((file) => (pre.textContent += `${file.name}\n`));
});