以 JavaScript 讀取檔案

選取使用者本機裝置上的檔案並進行互動,是最常用的網路功能之一。可讓使用者選取檔案並上傳到伺服器,例如分享相片或提交稅務文件時。也允許網站讀取及操控這些網站,而不需要在網路中傳輸資料。本頁面將逐步說明如何使用 JavaScript 與檔案互動。

新型 File System Access API

File System Access API 可讓使用者讀取及寫入使用者本機系統上的檔案和目錄。大部分以 Chromium 為基礎的瀏覽器 (例如 Chrome 和 Edge) 都支援這項技術。如要進一步瞭解,請參閱 File System Access API

由於 File System Access API 並非與所有瀏覽器都相容,建議您使用 browser-fs-access。在可以使用新 API 的情況下,這個輔助程式庫會在可用時採用,並在不適用時改回使用舊版方法。

輕鬆處理檔案

本指南將說明如何使用舊版 JavaScript 方法與檔案互動。

選取多個檔案

選取檔案主要有兩種方法:使用 HTML 輸入元素拖曳區域

HTML 輸入元素

使用者選取檔案最簡單的方法,就是使用 <input type="file"> 元素,所有主要瀏覽器都支援此元素。使用者點選之後,就能使用作業系統內建的檔案選取 UI,選取一或多個檔案 (如果內含 multiple 屬性)。使用者選取一或多個檔案後,就會觸發元素的 change 事件。您可以存取 event.target.files 中的檔案清單,即 FileList 物件。FileList 中的每個項目都是 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>

以下範例可讓使用者使用作業系統內建的檔案選取 UI 選取多個檔案,然後將每個選取的檔案記錄到控制台。

限制使用者可選取的檔案類型

在某些情況下,您可能會想限制使用者可選取的檔案類型。舉例來說,圖片編輯應用程式應該只接受圖片,而非文字檔。如要設定檔案類型限制,請在輸入元素中加入 accept 屬性,指定系統可接受的檔案類型:

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

自訂拖曳

在某些瀏覽器中,<input type="file"> 元素也是放置目標,可讓使用者將檔案拖曳到應用程式中。不過,這個放置目標較小,可能難以使用。相反地,使用 <input type="file"> 元素提供核心功能後,您就可以提供大型的自訂拖曳介面。

選擇放置區域

下降途徑取決於應用程式設計。您可能只想讓部分視窗成為掉落介面,但可以使用整個視窗。

Squoosh 的圖片壓縮網頁應用程式的螢幕截圖。
Squoosh 讓整個視窗變成放置區。

圖片壓縮應用程式 Squoosh 可讓使用者將圖片拖曳到視窗中的任何位置,然後按一下「Select a image」 (選取圖片) 來叫用 <input type="file"> 元素。無論您選擇哪種放置區域,請務必明確告知使用者,他們可以將檔案拖曳到該途徑上。

定義放置區

如要將元素設為拖曳區域,請為兩個事件建立事件監聽器:dragoverdropdragover 事件會更新瀏覽器 UI,以視覺方式表示拖曳動作正在建立檔案副本。使用者將檔案放到途徑上後,就會觸發 drop 事件。與輸入元素一樣,您可以從 event.dataTransfer.files 存取檔案清單,也就是 FileList 物件。FileList 中的每個項目都是 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() 會停止瀏覽器的預設行為,讓程式碼改為執行。如果沒有這類檔案,瀏覽器將會離開網頁,並開啟使用者拖放到瀏覽器視窗中的檔案。

如需實際示範,請參閱「自訂拖曳」。

那目錄呢?

很抱歉,不是良好的方式是使用 JavaScript 存取目錄。

<input type="file"> 元素的 webkitdirectory 屬性可讓使用者選擇目錄或目錄。除了 Android 版 Firefox 和 iOS 版 Safari 之外,大多數主要瀏覽器支援此功能。

如果已啟用拖曳功能,使用者可能會嘗試將目錄拖曳至放置區域。放置事件發生時,會包含該目錄的 File 物件,但不會提供目錄內任何檔案的存取權。

讀取檔案中繼資料

File 物件包含檔案的中繼資料。大多數瀏覽器都會提供檔案名稱、檔案大小和 MIME 類型,但視平台而定,不同的瀏覽器可能會提供不同或額外的資訊。

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

您可以在 input-type-file 示範中查看實際運作情形。

讀取檔案的內容

您可以使用 FileReaderFile 物件的內容讀取到記憶體中。您可以指示 FileReader陣列緩衝區資料網址文字的形式讀取檔案:

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

這個範例會讀取使用者提供的 File,然後將其轉換為資料網址,並使用該資料網址在 img 元素中顯示圖片。如要瞭解如何確認使用者已選取圖片檔,請參閱 read-image-file 示範。

監控檔案讀取的進度

讀取大型檔案時,建議您提供使用者體驗一些使用者體驗,讓使用者瞭解讀取的進度。這時請使用 FileReader 提供的 progress 事件。progress 事件有兩個屬性:loaded (讀取的數量) 和 total (讀取的數量)。

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

主頁橫幅由 Vincent Botta 在 Unsplash 網站上提供