Đọc tệp trong JavaScript

Việc 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. Tính năng này cho phép người dùng chọn tệp và tải tệp lên máy chủ, chẳng hạn như khi chia sẻ ảnh hoặc gửi tài liệu thuế. API này cũng cho phép các trang web đọc và thao tác với các tệp đó 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 và thư mục trên hệ thống cục bộ của người dùng. Tính năng này có trong hầu hết các trình duyệt dựa trên Chromium như Chrome và Edge. Để tìm hiểu thêm về API này, hãy tham khảo bài viết API truy cập hệ thống tệp.

Vì File System Access API không tương thích với tất cả trình duyệt, nên bạn nên sử dụng browser-fs-access, một thư viện trợ giúp sử dụng API mới bất cứ khi nào có thể và quay lại các phương pháp cũ khi không có.

Làm việc với tệp theo cách truyền thống

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

Chọn 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ử đầu vào 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 vào, nút này cho phép người dùng chọn một tệp hoặc nhiều tệp nếu thuộc tính multiple được đưa vào, 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. 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 vào danh sách tệp từ event.target.files, đây là một đố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 cho phép người dùng chọn nhiều tệp bằ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, sau đó ghi lại 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, bạn có thể muốn giới hạn các loại tệp mà người dùng có thể chọn. Ví dụ: ứng dụng chỉnh sửa hình ảnh chỉ nên chấp nhận hình ảnh, chứ không phải tệp văn bản. Để đặt các quy định 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 có kích thước 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 nền tảng kéo và thả tuỳ chỉnh lớn.

Chọn khu vực thả

Bề mặt thả của bạn phụ thuộc vào thiết kế của ứng dụng. Bạn có thể chỉ muốn một phần của cửa sổ là nền tảng 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 web nén hình ảnh.
Squoosh biến toàn bộ cửa sổ thành vùng thả.

Ứng dụng nén hình ảnh Squoosh cho phép người dùng kéo một hình ảnh bất kỳ vào cửa sổ rồi nhấp vào chọn một hình ảnh để gọi phần tử <input type="file">. Dù bạn chọn vùng thả nào, 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 thả

Để 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 thấy rõ rằng thao tác kéo và thả đang tạo 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. Cũng như phần tử đầu vào, bạn có thể truy cập vào danh sách tệp từ event.dataTransfer.files, đây là một đố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() sẽ dừng hành vi mặc định của trình duyệt và cho phép mã của bạn chạy. Nếu không có các tệp này, trình duyệt sẽ rời 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 minh hoạ trực tiếp, hãy tham khảo phần 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 tốt để 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. Tính năng này được hỗ trợ trong hầu hết các trình duyệt lớn 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ả, 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ả 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 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, các trình duyệt khác nhau có thể cung cấp thông tin khác nhau hoặc 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 vào bộ nhớ. Bạn có thể yêu cầu FileReader đọc một 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 một 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 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 tiến trình đọc đã diễn ra như thế nào. Để làm 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 Botta trên Unsplash