JavaScript でファイルを読み込む方法

JavaScript の File API を使うと、選択したファイルの内容を読み込むことができます。 ここでは、File API の基本的な利用方法を説明します。

それでは、さっそく File API を使い方を見てみましょう。

JavaScript でファイルを読み込む概要

input 要素でファイルを選択する

HTML では input 要素の type 属性に file を設定すると、ファイルを選択することができます。

<input type="file">

複数のファイル選択を許可する場合は、multiple 属性を設定します。

<input type="file" multiple>

ユーザーがファイルの選択操作をした後には、change イベントが発生します。

input 要素の change イベントを処理する

change イベントを捕えるには、まず次のように onchange に直接ハンドラを記述する方法があります。 次のように onchange のハンドラに this を渡すことで、ハンドラ内で直ちに input 要素にアクセスできます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>File Input Test</title>
    <script>
      function fileChanged(input) {
        console.log(input);
        for (let i = 0; i < input.files.length; i++) {
          console.log(input.files[i]);
        }
      }
    </script>
  </head>
  <body>
    <input type="file" onchange="fileChanged(this)" multiple/>
  </body>
</html>

また、動的に input 要素を取得して、change イベントを処理することもできます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>File Input Test</title>
    <script>
      window.addEventListener('load', () => {
        const f = document.getElementById('file1');
        f.addEventListener('change', evt => {
          const input = evt.target;
          for (let i = 0; i < input.files.length; i++) {
            console.log(input.files[i]);
          }
        });
      });
    </script>
  </head>
  <body>
    <input type="file" id="file1" multiple/>
  </body>
</html>

この場合はイベントオブジェクトの target 属性に input 要素が渡されます。

input 要素の files プロパティから File オブジェクトを取得

change イベントが発生したとき、input 要素の files プロパティに、選択されたファイルを表現する File オブジェクトが渡されます。

ファイルの選択画面を一旦表示して、ファイルを選択しないでキャンセルボタンを押した場合にも change イベントハンドラは呼び出されますが、files プロパティは長さ 0 の配列となります。

File オブジェクトはどこかに保存しておき、後で使うこともできます。

FileReader で File オブジェクトを読み込む

FileReader オブジェクトを使うと File オブジェクトの中身を読み込むことができます。FileReader オブジェクトのファイル読み込み用のメソッドに、File オブジェクトを渡すことで読み取りを開始します。

ファイル読み込み用のメソッドには、次の種類があります。

  • readAsText()
  • readAsArrayBuffer()
  • readAsBinaryString()
  • readAsDataURL()

FileReader オブジェクトは Blob のデータを読み取ります。File オブジェクトは Blob から派生しているので、Blob でもあります。

FileReader オブジェクトがファイルを読み込むと、onload イベントが呼び出されます。 読み込んだ結果は、FileReader オブジェクトの result プロパティにセットされます。

readAsText() で読み込んだら result に文字列がセットされ、readAsArrayBuffer() で読み込んだら result には ArrayBuffer オブジェクトがセットされます。

それでは、実際にファイルを読み込む方法をみてみましょう。

ファイルを読み込む例

選択したファイルを読み込み表示する例

ここでは次のような動きをするサンプルを作ります。

ファイルを選んだら、選んだファイルの内容が表示されます。

今回動作確認で使用する HTML ファイルとスタイルシートの中身を示します。

HTML ファイルは次の通りです。 file1.html として保存してください。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>File API 1</title>
    <script src="file1.js"></script>
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <input type="file" id="file1" />
    <pre id="pre1"></pre>
  </body>
</html>

input 要素の type 属性を file として、ID として file1 をセットしています。

スタイルシート style.css は次のような内容です。少しフォントを変えたり、背景色を付けているだけで、あまりたいしたことをしていません。

@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
@import url('https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap');

html {
    font-family: 'Roboto', sans-serif;
}

pre {
    font-family: 'Roboto mono', monospace;
    font-size: 0.9rem;
    background-color: #D6EAF8;
    padding: 10px;
}

JavaScript ファイル file1.js は次の通りです。

window.addEventListener('load', () => {
  const f = document.getElementById('file1');
  f.addEventListener('change', evt => {
    let input = evt.target;
    if (input.files.length == 0) {
      console.log('No file selected');
      return;
    }
    const file = input.files[0];
    const reader = new FileReader();
    reader.onload = () => {
      const pre = document.getElementById('pre1');
      pre.innerHTML = reader.result;
    };

    reader.readAsText(file);
  });
});

window オブジェクトの load イベントハンドラにて、このファイル・インプット要素の change イベントハンドラを設定しています。

これによって、ファイルの選択操作を行ったときに、change イベントハンドラが呼び出されます。

イベントハンドラには Event オブジェクトが渡されます。イベントオブジェクトの target 属性に、ファイルの選択操作を行ったインプット要素が渡されます。 このインプット要素の files プロパティに、選択された File オブジェクトのリストが設定されます

今回は input 要素には multiple 属性を設定していないので、File オブジェクトは最大で 1 つだけセットされます。

File オブジェクトを取得した後、ファイルを読み込む箇所は次の部分です。

const file = input.files[0]; // File オブジェクト
const reader = new FileReader(); // FileReader オブジェクト
reader.onload = () => { // 読みんこんだ後のコールバック
    const pre = document.getElementById('pre1');
    pre.innerHTML = reader.result;
};

reader.readAsText(file); // 読み込み開始

これによって、pre 要素内に、ファイルの内容がセットされます。

イベント処理と Promise の利用

さて、上のコードでは FileReader オブジェクトの readAsText() メソッドを呼ぶ前に onload イベントハンドラをセットしました。

FileReader にはもっと様々なイベントが用意されていますが、それらを毎回セットしてから呼び出すと少々煩雑になりがちです。

そこで、Promise を利用してコードをもう少し整理してみましょう。

File オブジェクトを受け取る次のような関数オブジェクトを用意します。

const readAsTextReader = file => {
  const reader = new FileReader();

  return new Promise((resolve, reject) => {
    reader.onloadstart = ev => {
      console.log(`onloadstart: total=${ev.loaded}/${ev.loaded}`);
    };

    reader.onloadend = ev => {
      console.log(`onloadend: total=${ev.loaded}/${ev.loaded}`);
    };

    reader.onprogress = ev => {
      console.log(`onprogress: total=${ev.loaded}/${ev.loaded}`);
    };

    reader.onerror = () => {
      reader.abort();
      reject('Unknown error occurred during reading the file');
    };

    reader.onload = () => {
      console.log('onload');
      resolve(reader.result);
    };

    reader.readAsText(file);
  });
};

これを用いると、ファイルの読み込み時の非同期処理が Promise の then と catch を用いて、次のようにかけます。

window.addEventListener('load', () => {
  const f = document.getElementById('file1');
  f.addEventListener('change', evt => {
    const input = evt.target;
    if (input.files.length == 0) {
      return;
    }
    const file = input.files[0];
    console.log(file);

    readAsTextReader(file)
      .then(value => {
        const pre = document.getElementById('pre1');
        pre.innerHTML = value;
      })
      .catch(reason => {
        alert(reason);
      });
  });
});

簡易バイナリファイルビューア

次にテキストファイルだけではなく、バイナリファイルも読み込む例として、簡単なバイナリファイルビューアみたいなものを作りましょう。

ファイルを選択して、FileReader で読むところは一緒です。

ただ、今回は readAsArrayBuffer() でファイルを読み込みます。その結果は ArrayBuffer として返ります。

ArrayBuffer は JavaScript でのバイト列といえます。詳しくは「ArrayBuffer でバイナリデータを扱う」をみてください。

const readAsArrayBufferReader = file => {
  const reader = new FileReader();

  return new Promise((resolve, reject) => {
    reader.onerror = () => {
      reader.abort();
      reject('Unknown error occurred during reading the file');
    };

    reader.onload = () => {
      console.log('onload');
      resolve(reader.result);
    };

    reader.readAsArrayBuffer(file);
  });
};

window.addEventListener('load', () => {
  const f = document.getElementById('file1');
  f.addEventListener('change', evt => {
    const input = evt.target;
    if (input.files.length == 0) {
      return;
    }
    let file = input.files[0];
    console.log(file);
    if (!file) {
      return;
    }
    if (file.size > 50 * 1024) {
      alert('Please select a file smaller than 50kb.');
      return;
    }

    readAsArrayBufferReader(file)
      .then(buff => {
        console.log(buff);
        let s = '';
        let a = new Uint8Array(buff);
        for (let i = 0; i * 16 < a.length; i++) {
          let line = '';
          let p = i * 16;
          let b = a.slice(p, Math.min(a.length, p + 16));
          for (const e of b) {
            let h = e
              .toString(16)
              .toUpperCase()
              .padStart(2, '0');
            line += ' ' + h;
          }
          let addr = p
            .toString(16)
            .toUpperCase()
            .padStart(8, '0');
          line = `${addr}:${line}\n`;
          s += line;
        }
        let pre = document.getElementById('pre1');
        pre.innerHTML = s;
      })
      .catch(reason => {
        alert(reason);
      });
  });
});

今回はファイルサイズは 50KB までに制限しています。

ArrayBuffer として読み出したデータから、Uint8Array オブジェクトを作成。 そこから slice() を使って 16 バイトずつ表示用に整形しています。

以上で、JavaScript で File オブジェクトと FileReader を使ってファイルを読み出す方法と、 Promise を使って非同期処理を簡易化する方法と、バイナリデータを扱う例を紹介しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 JavaScript 入門