JavaScript でファイルを読み取る

ユーザーのローカル デバイスでファイルを選択して操作することは、ウェブで最もよく使用されている機能の 1 つです。ユーザーは、写真の共有や税務書類の送信などの際に、ファイルを選択してサーバーにアップロードできます。また、サイトはネットワーク経由でデータを転送しなくても、データを読み取って操作できます。このページでは、JavaScript を使用してファイルを操作する方法について説明します。

最新の File System Access API

File System Access API は、ユーザーのローカル システム上にあるファイルとディレクトリの読み書きを行います。この機能は Chrome や Edge など、Chromium ベースのほとんどのブラウザで利用できます。詳細については、File System Access API をご覧ください。

File System Access API はすべてのブラウザに対応しているわけではないため、browser-fs-access の使用をおすすめします。このヘルパー ライブラリは、新しい API が利用可能な場合は常に新しい API を使用し、使用できない場合は以前のアプローチにフォールバックします。

従来の方法でファイルを操作する

このガイドでは、従来の JavaScript メソッドを使用してファイルを操作する方法について説明します。

ファイルの選択

ファイルを選択する主な方法は 2 つあります。HTML 入力要素を使用する方法と、ドラッグ&ドロップ ゾーンを使用する方法です。

HTML 入力要素

ユーザーがファイルを選択する最も簡単な方法は、すべての主要なブラウザでサポートされている <input type="file"> 要素を使用することです。ユーザーがクリックすると、オペレーティング システムの組み込みのファイル選択 UI を使用して、1 つまたは複数のファイルを選択できます(multiple 属性が含まれている場合)。ユーザーが 1 つまたは複数のファイルの選択を完了すると、要素の change イベントが発生します。ファイルのリストには、FileList オブジェクトである event.target.files からアクセスできます。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 を使用すると、ユーザーはウィンドウ内の任意の場所に画像をドラッグし、[画像を選択] をクリックして <input type="file"> 要素を呼び出すことができます。ドロップゾーンとしてどのゾーンを選択するにしても、そのサーフェスにファイルをドラッグできることがユーザーにとって明確であることを確認してください。

ドロップゾーンを定義する

要素をドラッグ&ドロップ ゾーンとして有効にするには、2 つのイベント(dragoverdrop)のリスナーを作成します。dragover イベントによってブラウザ UI が更新され、ドラッグ&ドロップ アクションによってファイルのコピーが作成されていることが視覚的に示されます。drop イベントは、ユーザーがファイルをサーフェスにドロップすると呼び出されます。入力要素と同様に、FileList オブジェクトである event.dataTransfer.files からファイルのリストにアクセスできます。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 属性を使用すると、ユーザーは 1 つまたは複数のディレクトリを選択できます。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 デモで実際に確認できます。

ファイルの内容を読み取る

FileReader を使用して、File オブジェクトのコンテンツをメモリに読み取ります。ファイルを配列バッファデータ URLテキストとして読み取るように 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 を読み取り、それをデータ URL に変換し、そのデータ URL を使用して img 要素内に画像を表示します。ユーザーが画像ファイルを選択していることを確認する方法については、read-image-file デモをご覧ください。

ファイルの読み取りの進行状況をモニタリングする

大きなファイルを読み取るときは、読み取りの進捗状況をユーザーに伝える UX を用意すると便利です。そのためには、FileReader が提供する progress イベントを使用します。progress イベントには、loaded(読み取り量)と total(読み取り量)の 2 つのプロパティがあります。

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