Đọc tệp trong JavaScript

Chọn và tương tác với các tệp trên thiết bị cục bộ của người dùng là một trong những tính năng được sử dụng phổ biến nhất trên web. API này cho phép người dùng chọn tệp và tải tệp lên máy chủ, ví dụ: khi chia sẻ ảnh hoặc gửi giấy tờ thuế. API này cũng cho phép các trang web đọc và thao tác trên chúng mà không cần phải chuyển dữ liệu qua mạng. Trang này hướng dẫn cách sử dụng JavaScript để tương tác với các tệp.

API Truy cập hệ thống tệp hiện đại

API Truy cập hệ thống tệp cung cấp một cách để đọc và ghi vào các tệp cũng như thư mục trên hệ thống cục bộ của người dùng. Tính năng này có trên hầu hết các trình duyệt dựa trên Chromium, chẳng hạn như Chrome và Edge. Để tìm hiểu thêm về API này, hãy tham khảo API Truy cập hệ thống tệp.

Do API Truy cập hệ thống tệp không tương thích với mọi trình duyệt, bạn nên sử dụng browser-fs-access, một thư viện trợ giúp sử dụng API mới nếu có và quay lại các phương pháp cũ nếu không có.

Làm việc với tệp theo cách cũ

Hướng dẫn này chỉ cho bạn cách tương tác với các tệp bằng các phương thức JavaScript cũ.

Chọn các tệp

Có hai cách chính để chọn tệp: sử dụng phần tử nhập HTML và sử dụng vùng kéo và thả.

Phần tử nhập HTML

Cách dễ nhất để người dùng chọn tệp là sử dụng phần tử <input type="file">. Phần tử này được hỗ trợ trong mọi trình duyệt chính. Khi được nhấp, tính năng này cho phép người dùng chọn một hoặc nhiều tệp nếu thuộc tính multiple được bao gồm, sử dụng giao diện người dùng chọn tệp tích hợp sẵn trong hệ điều hành của họ. Khi người dùng chọn xong một hoặc nhiều tệp, sự kiện change của phần tử đó sẽ kích hoạt. Bạn có thể truy cập danh sách tệp trong event.target.files, đây là đối tượng FileList. Mỗi mục trong FileList là một đối tượng File.

<!-- The `multiple` attribute lets users select multiple files. -->
<input type="file" id="file-selector" multiple>
<script>
  const fileSelector = document.getElementById('file-selector');
  fileSelector.addEventListener('change', (event) => {
    const fileList = event.target.files;
    console.log(fileList);
  });
</script>

Ví dụ sau đây cho phép người dùng chọn nhiều tệp bằng cách sử dụng giao diện người dùng chọn tệp tích hợp sẵn của hệ điều hành, rồi ghi nhật ký từng tệp đã chọn vào bảng điều khiển.

Giới hạn các loại tệp mà người dùng có thể chọn

Trong một số trường hợp, có thể bạn sẽ muốn giới hạn các loại tệp mà người dùng có thể chọn. Ví dụ: một ứng dụng chỉnh sửa hình ảnh chỉ chấp nhận hình ảnh, chứ không chấp nhận tệp văn bản. Để đặt các hạn chế về loại tệp, hãy thêm thuộc tính accept vào phần tử đầu vào để chỉ định những loại tệp được chấp nhận:

<input type="file" id="file-selector" accept=".jpg, .jpeg, .png">

Kéo và thả tuỳ chỉnh

Trong một số trình duyệt, phần tử <input type="file"> cũng là mục tiêu thả, cho phép người dùng kéo và thả tệp vào ứng dụng của bạn. Tuy nhiên, mục tiêu thả này nhỏ và có thể khó sử dụng. Thay vào đó, sau khi cung cấp các tính năng cốt lõi bằng phần tử <input type="file">, bạn có thể cung cấp một giao diện kéo và thả lớn, tuỳ chỉnh.

Chọn vùng giảm

Bề mặt thả phụ thuộc vào thiết kế của ứng dụng. Có thể bạn chỉ muốn một phần của cửa sổ là giao diện thả, nhưng bạn có thể sử dụng toàn bộ cửa sổ.

Ảnh chụp màn hình Squoosh, một ứng dụng nén hình ảnh trên web.
Squoosh biến toàn bộ cửa sổ thành một vùng bỏ qua.

Ứng dụng nén hình ảnh Squoosh cho phép người dùng kéo một hình ảnh vào vị trí bất kỳ trong cửa sổ rồi nhấp vào chọn một hình ảnh để gọi phần tử <input type="file">. Bất kể bạn chọn vùng thả xuống là gì, hãy đảm bảo người dùng hiểu rõ rằng họ có thể kéo tệp vào nền tảng đó.

Xác định vùng giảm

Để bật một phần tử dưới dạng vùng kéo và thả, hãy tạo trình nghe cho hai sự kiện: dragoverdrop. Sự kiện dragover cập nhật giao diện người dùng của trình duyệt để cho biết một cách trực quan rằng thao tác kéo và thả đang tạo ra một bản sao của tệp. Sự kiện drop sẽ kích hoạt sau khi người dùng thả các tệp vào nền tảng. Tương tự như với phần tử đầu vào, bạn có thể truy cập danh sách các tệp của event.dataTransfer.files. Đây là đối tượng FileList. Mỗi mục trong FileList là một đối tượng File.

const dropArea = document.getElementById('drop-area');

dropArea.addEventListener('dragover', (event) => {
  event.stopPropagation();
  event.preventDefault();
  // Style the drag-and-drop as a "copy file" operation.
  event.dataTransfer.dropEffect = 'copy';
});

dropArea.addEventListener('drop', (event) => {
  event.stopPropagation();
  event.preventDefault();
  const fileList = event.dataTransfer.files;
  console.log(fileList);
});

event.stopPropagation()event.preventDefault() dừng hành vi mặc định của trình duyệt và thay vào đó để mã của bạn chạy. Nếu không có các đường dẫn này, trình duyệt sẽ chuyển hướng khỏi trang của bạn và mở các tệp mà người dùng đã thả vào cửa sổ trình duyệt.

Để xem phần minh hoạ trực tiếp, hãy tham khảo bài viết Kéo và thả tuỳ chỉnh.

Còn thư mục thì sao?

Rất tiếc, không có cách nào hay để truy cập vào thư mục bằng JavaScript.

Thuộc tính webkitdirectory trên phần tử <input type="file"> cho phép người dùng chọn một hoặc nhiều thư mục. API này được hỗ trợ trong hầu hết các trình duyệt chính, ngoại trừ Firefox cho Android và Safari trên iOS.

Nếu bạn bật tính năng kéo và thả, thì người dùng có thể cố gắng kéo một thư mục vào vùng thả. Khi sự kiện thả được kích hoạt, sự kiện này sẽ bao gồm một đối tượng File cho thư mục, nhưng không cung cấp quyền truy cập vào bất kỳ tệp nào trong thư mục đó.

Đọc siêu dữ liệu tệp

Đối tượng File chứa siêu dữ liệu về tệp. Hầu hết các trình duyệt đều cung cấp tên tệp, kích thước tệp và loại MIME, mặc dù tuỳ thuộc vào nền tảng, mỗi trình duyệt có thể cung cấp thông tin khác nhau hoặc thông tin bổ sung.

function getMetadataForFileList(fileList) {
  for (const file of fileList) {
    // Not supported in Safari for iOS.
    const name = file.name ? file.name : 'NOT SUPPORTED';
    // Not supported in Firefox for Android or Opera for Android.
    const type = file.type ? file.type : 'NOT SUPPORTED';
    // Unknown cross-browser support.
    const size = file.size ? file.size : 'NOT SUPPORTED';
    console.log({file, name, type, size});
  }
}

Bạn có thể xem ví dụ thực tế trong bản minh hoạ input-type-file.

Đọc nội dung của tệp

Sử dụng FileReader để đọc nội dung của đối tượng File trong bộ nhớ. Bạn có thể yêu cầu FileReader đọc tệp dưới dạng vùng đệm mảng, URL dữ liệu hoặc văn bản:

function readImage(file) {
  // Check if the file is an image.
  if (file.type && !file.type.startsWith('image/')) {
    console.log('File is not an image.', file.type, file);
    return;
  }

  const reader = new FileReader();
  reader.addEventListener('load', (event) => {
    img.src = event.target.result;
  });
  reader.readAsDataURL(file);
}

Ví dụ này đọc File do người dùng cung cấp, sau đó chuyển đổi thành URL dữ liệu và sử dụng URL dữ liệu đó để hiển thị hình ảnh trong phần tử img. Để tìm hiểu cách xác minh rằng người dùng đã chọn tệp hình ảnh, hãy tham khảo bản minh hoạ read-image-file.

Theo dõi tiến trình đọc tệp

Khi đọc các tệp có kích thước lớn, bạn nên cung cấp một số trải nghiệm người dùng để cho người dùng biết quá trình đọc đã tiến triển đến đâu. Để làm được việc đó, hãy sử dụng sự kiện progress do FileReader cung cấp. Sự kiện progress có hai thuộc tính: loaded (lượng đã đọc) và total (lượng cần đọc).

function readFile(file) {
  const reader = new FileReader();
  reader.addEventListener('load', (event) => {
    const result = event.target.result;
    // Do something with result
  });

  reader.addEventListener('progress', (event) => {
    if (event.loaded && event.total) {
      const percent = (event.loaded / event.total) * 100;
      console.log(`Progress: ${Math.round(percent)}`);
    }
  });
  reader.readAsDataURL(file);
}

Hình ảnh chính của Vincent Bcott từ Unsplash