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 を使用し、利用できない場合は従来のアプローチにフォールバックします。

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

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

ファイルを選択

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

HTML 入力要素

ユーザーがファイルを選択する最も簡単な方法は、<input type="file"> 要素を使用することです。これはすべての主要なブラウザでサポートされています。クリックすると、オペレーティング システムの組み込みファイル選択 UI を使用して、ユーザーがファイル(multiple 属性が含まれている場合は複数のファイル)を選択できます。ユーザーがファイルの選択を完了すると、要素の 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"> 要素を呼び出すことができます。ドロップゾーンとしてどのゾーンを選んでも、そのサーフェスにファイルをドラッグできることをユーザーに明示する必要があります。

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

要素をドラッグ&ドロップ ゾーンとして有効にするには、dragover イベントと drop イベントのリスナーを作成します。dragover イベントは、ドラッグ&ドロップ アクションによってファイルのコピーが作成されていることを視覚的に示すように、ブラウザ UI を更新します。drop イベントは、ユーザーがサーフェスにファイルをドロップした後に発生します。input 要素と同様に、event.dataTransfer.filesFileList オブジェクト)からファイルのリストにアクセスできます。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 デモで実際に確認できます。

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

FileReader を使用して、File オブジェクトのコンテンツをメモリに読み込みます。FileReader に、配列バッファデータ URL、またはテキストとしてファイルを読み取るように指示できます。

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