現役エンジニアが教える!実践JavaScript入門 〜ブラウザでのファイル操作編〜
今回扱う内容:ブラウザでのファイル操作
ブラウザでのファイル操作を理解しよう。
はじめに
私たちは普段からプロフィールページの登録に画像ファイルを選択したり、Webサービスからデータをダウンロードしてくるなど、ファイル操作を日常的に行います。
今回はWeb開発でもよく使われるファイル操作についてこちらのMDNの記事を参考に解説していきます。
https://developer.mozilla.org/ja/docs/Web/API/File_API/Using_files_from_web_applications
用語の説明
一般的にファイル選択の実装をするには、こちらのMDNの記事にあるようにinput要素の type
属性に file
を指定したものを使います。そして選択されたファイルをどう扱うかという動きについてはJavaScriptで記述します。
この節ではJavaScriptでファイルを扱う際に重要な用語について紹介します。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/file
FileオブジェクトとBlobオブジェクト
Fileオブジェクトとは先程のinput
要素のtype
属性にfile
を指定した際に取得できる値の型です。
ファイルについての情報を提供したり、Fileオブジェクトを通じてWebページ内のJavaScriptからファイルの内容にアクセスできます。
なのでファイルを扱うためにブラウザで用意されているAPIだと思ってもらえれば大丈夫です。
一方でBlobオブジェクトは、Fileオブジェクトのベースになっているものであり、FileオブジェクトはBlobオブジェクトを継承しています。
Blobオブジェクトはブラウザからファイルのデータを作るとき、またインターネットから落としてきたデータをファイルとして扱うときに、新しくBlobオブジェクトを作成します。
説明だけではわかりにくい部分もあるので、次節で実際のファイル操作の例とともに2つの違いを見ていきましょう。
https://developer.mozilla.org/ja/docs/Web/API/File
https://developer.mozilla.org/ja/docs/Web/API/Blob
ファイル操作の例
画像ファイルを扱う
まず初めにファイル操作の例として画像ファイルの扱い方について見ていきます。
htmlファイルでinput
タグを用意し、JavaScriptファイルにinput
タグの参照と次のようなEventListenerを登録します。
ファイルを選択することでe.target.files
からファイル情報を参照できます。またファイルを複数選択した場合も配列としてファイル情報を読み取ることができます。
<input type="file" multiple> <!-- multiple: ファイルの複数選択可 -->
<div class="preview"></div> <!--画像を表示させる際に使用-->
const inputRef = document.querySelector("input");
inputRef.addEventListener("change", (e) => {
console.log(e);
console.log(e.target);
console.log(e.target.files); //選択したファイル情報が表示される
});
次にファイル情報を画面に表示させてみます。
画像の表示
パターン1
次のように記述することで、選択した画像ファイルを図のように表示させることができます。
複数選択した画像はFileListという型で呼ばれ、for...of
を使うことでファイル1つずつを取り出して扱うことができます。
次にURL.createObjectURL
メソッドに取り出したファイルを渡すことで、画像ファイルのURLを取得できます。
画像の表示には通常img
タグのsrc
に画像が置かれているURLを参照させます。一方でローカルのファイルを参照させる場合はURL.createObjectURL
メソッドにFileオブジェクトを渡すことでローカルのブラウザ内でのURLを生成し、生成したURLをsrc
に参照させます。
この処理を選択したファイルの数だけ実行するので選択したファイル全てを画面に表示できます。
const previewRef = document.querySelector(".preview");
const handleImageFiles1 = (e) => {
for (const file of e.target.files) {
const item = document.createElement("div");
const image = document.createElement("img");
//URLを作成
image.src = URL.createObjectURL(file);
item.appendChild(image);
previewRef.appendChild(item);
}
};
inputRef.addEventListener("change", handleImageFiles1);
詳しいcreateObjectURL
メソッドの内容についてはMDNの記事を参考にしてみてください。
https://developer.mozilla.org/ja/docs/Web/API/URL/createObjectURL
パターン2
次のように記述してもパターン1と同様に選択した画像を表示させることができます。
こちらではFileReader
オブジェクトを使っており、新しく作成したオブジェクトにreadAsDataURL
メソッドを介してFileオブジェクトを渡すことでloadイベントが実行されます。実行内容は各画像ファイルを読み込んだ結果がresult
の中に入っているので先ほどと同様にsrc
に結果を渡して画像を表示します。
const handleImageFiles2 = (e) => {
for (const file of e.target.files) {
const item = document.createElement("div");
const image = document.createElement("img");
const reader = new FileReader();
reader.addEventListener("load", () => {
image.src = reader.result;
item.appendChild(image);
previewRef.appendChild(item);
});
reader.readAsDataURL(file);
}
}
詳しいFileReader
オブジェクトについてはMDNの記事を参考にしてみてください。
https://developer.mozilla.org/ja/docs/Web/API/FileReader
https://developer.mozilla.org/ja/docs/Web/API/FileReader/readAsDataURL
createObjectURLとreadAsDataURLの違い
2パターン画像表示について紹介しましたが2つの手法は、createObjectURL
はBlobを参照するURLを生成し、readAsDataURL
はデータURLを読み取るという点に違いがあります。パターン1とパターン2で作成したimg.src
をコンソールに表示させると分かるのですが、createObjectURL
では作られたURLには画像データは含まれておらずデータの参照先だけが生成されます。一方でreadAsDataURL
では作られたURLに画像のデータが入っています。
というのも画像データはテキストに変換でき、そのデータ形式を「BASE64」といいます。そしてreadAsDataURL
では画像のテキストデータをURLに含ませています。
それぞれのデメリットとしては、createObjectURL
はメモリの扱い方に注意が必要です。Blobを参照するURLを生成するとメモリ上にデータが残ってしまうのでメモリの解放を適宜行わないとパフォーマンスの劣化につながります。一方でreadAsDataURL
ではデータのサイズが大きくなつてしまう点に注意が必要です。画像ファイルをテキストに変換するとファイルサイズが通常20%〜30%増えてしまうという問題があります。適宜使用目的に合わせて使い分けてください。
テキストファイルを扱う
次にテキストファイルの扱い方について見ていきます。今回は次のような内容のcsvファイルを読み込み、画像と同様にブラウザに表示させます。
csvファイルの中身
テキストの表示
コード内容は画像の表示で記述したものとほぼ同じで、次のようになっています。新しく作成したFileReader
オブジェクトにreadAsText
メソッドを介してloadイベントが実行されます。readAsText
メソッドではファイルの中身をテキストとして読むことができます。そしてsplit
とmap
を使ってリスト処理を行うことでテキストの中身を3×3の2次元配列に変換し、join(",")
を使って「,」繋がりのテキストに戻してからブラウザに表示します。
リスト処理についてはJSマスター第5回を参照してください。
const inputRef = document.querySelector("input");
const previewRef = document.querySelector(".preview");
const hadleCsvFiles = (e) => {
for (const file of e.target.files) {
//URLを作成
const reader = new FileReader();
reader.addEventListener("load", () => {
const result = reader.result;
console.log(result);
const lines = result.split(/\n/).map((line) => line.split(","));
console.log(JSON.stringify(lines));
console.log(lines);
lines.forEach((line) => {
const item = document.createElement("p");
item.textContent = line.join(",");
previewRef.appendChild(item);
});
});
reader.readAsText(file)
}
};
inputRef.addEventListener("change", handleCsvFiles);
ファイルを保存する
最後にファイルの保存について見ていきます。今回先程のcsvのファイルの内容と同じデータをdata
という変数に持たせています。このデータをcsv形式としてダウンロードするという機能を実装します。コードは次のようになっており、はじめにcsvデータはテキストデータであるので定義したdata
をreduce
,join
,slice
を使うことでテキストに変換します。次にBlobオブジェクトを作成します。この時文字化け対策のためbomというデータを先頭につけます。また第2引数のオブジェクトにはtype
を渡しており、text/csv
としています。このように記述することでnew Blob
よりbomとcsvデータを足したデータのBlobオブジェクトを作成します。最後にaタグのdownload
プロパティにファイル名を渡しclickを実行することで、画面上でもaタグのダウンロードリンクが押されたのと同じ処理を実行できます。このように自分でファイルを作る際にはBlobオブジェクトを作ります。
const data = [
["0001", "itemA", "500yen"],
["0002", "itemB", "300yen"],
["0003", "itemC", "400yen"],
];
const handleCsvDownload = () => {
const csvData = data
.reduce((a, b) => a + b.join(",") + "\n", "")
.slice(0, -1);
const a = document.createElement("a");
const bom = new Unit8Array([0xef, 0xbb, 0xbf]); //文字化け対策
const blob = new Blob([bom, csvData], {type: "text/csv"});
a.href = URL.createObjectURL(blob); //URLを作成
a.download = "download.csv";
a.click();
}
buttonRef.addEventListener("click", handleCsvDownload)
またファイルの保存方法としてライブラリを使う方法もあります。今回はFileSaverというライブラリを使った方法をご紹介します。
https://github.com/eligrey/FileSaver.js/
使い方として一番簡単なやり方は次のようにscriptタグに読み込ませるというやり方です。
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>
すると先程のコードでaタグに関する記述がいらなくなるので`handleCsvDownload`関数を次のように書き換えるだけでファイルの保存が実装できます。また、aタグが使えないブラウザ環境もある一方で、FileSaverでは様々なブラウザに対応しているので、現実的には実績のあるライブラリを使うことをお勧めします。
const handleCsvDownload = () => {
const csvData = data
.reduce((a, b) => a + b.join(",") + "\n", "")
.slice(0, -1);
const bom = new Unit8Array([0xef, 0xbb, 0xbf]);
const blob = new Blob([bom, csvData], {type: "text/csv"});
saveAs(blob, "download.csv") //ファイルの保存
}
まとめ
ブラウザでファイル操作をする方法について解説しました。今回はコードの簡略化のため、ファイルサイズの制限やファイル拡張子の指定などを行いませんでしたが、実際の開発ではそういった点に注意しながら本内容を役立てていただけると嬉しいです。
次回はブラウザ上でのデータ保存について解説します。