HTML5のFile API
2021/04/04更新
目次
概要
File APIは、HTML5でブラウザからユーザーのローカルファイルにアクセスしたりするためのAPIである。
ユーザーが、ファイル選択フォームやドラッグ&ドロップなどによって選択したローカルファイルをその場で読み取って表示したり、ブラウザ上でファイルを作成してユーザーにダウンロードさせたりすることができる。
BlobとFile
Blobオブジェクト
Blobは、巨大なバイナリデータの塊を表すためのクラスである。「Blob」とは「Binary Large Object」に由来する。Blobオブジェクトは、データそのものであるバッファと、データのContent-Typeの情報を持っている。
Blobオブジェクトを作成するには、以下のようにする。
const blob = new Blob(
[buf1, buf2, …], // バッファの配列
{ type: 'text/plain' } // Content-Type
);
第1引数の配列で渡したバッファを全て結合したものが、このBlobのバッファとなる。バッファが1つだけの場合でも必ず配列にして渡す必要があるので注意が必要である。バッファとして指定可能なものは以下があり、混在していてもよい。
ArrayBufferまたは型付き配列(JavaScriptの型付き配列を参照)他の
Blob文字列(UTF-8のバイト列となる)
第2引数は省略可能で、デフォルトのContent-Typeは空文字列となる。
Blobオブジェクトは、sizeプロパティでデータのサイズ(byte)、typeプロパティでContent-Typeを確認できる。また、slice()メソッドで一部分の切り出しや、全体の複製ができる。
Fileオブジェクト
Fileは、Blobを継承したクラスで、Blobの持つ機能に加えて、さらにファイル名と最終更新日時をプロパティとして持たせたものである。
Fileオブジェクトを作成するには、以下のようにする。
const file = new File(
[buf1, buf2, …], // バッファの配列(Blobと同様)
"hoge.txt", // ファイル名
{
type: "text/plain", // Content-Type(Blobと同様)
lastModified: Date.now() // 更新日時
}
});
更新日時は、ミリ秒単位のUnix Timeで、デフォルトでは現在時刻となる。
Fileオブジェクトのプロパティは、Blobと同様のsize、typeに加えて、nameでファイル名、lastModifiedで更新日時を取得できる。
Fileオブジェクトは、仮想的なファイル1個を表すオブジェクトである。Fileオブジェクトを作成することで、JavaScript上でファイルを作成して、ユーザーにダウンロードさせることなどが可能となる。ユーザーのローカルファイルにアクセスするには、ユーザーが起こしたイベントからFileオブジェクトを取得する必要があり、詳細は次節で述べる。
Fileの取得方法
ユーザーのローカルファイルを表すFileオブジェクトは、ユーザーが自らファイルを選択するなどした場合にのみ取得できる(さもなければ、ウェブサイトからローカルファイルにアクセスし放題となり、大変危険な世界になってしまう)。
フォームのファイル選択から取得
フォーム部品の一つであるファイル選択要素(<input type="file">)のfilesプロパティを読み取ると、現在選択されているファイルのFileオブジェクトを束ねたFileListオブジェクトが取得できる。通常は、FileListには1つのFileオブジェクトが入るだけだが、<input>タグにmultiple属性を付けて複数ファイルを選択可能にしたときは、FileListには複数のFileが含まれている。
// 以下のHTMLによって、ユーザーがファイルを選択したとする。
// <input type="file" id="file_input">
const inputElement = document.getElementById('file_input');
// filesプロパティでFileオブジェクトのリストが得られる
const fileList = inputElement.files;
for (let i = 0; fileList.length > i; i++) {
// ユーザーが選択したファイルのFileオブジェクト
const file = fileList[i];
// ファイル名などが得られる
console.log(file.name);
…
}
ブラウザからのファイルアップロードいろいろに使用例を記載しているので、そちらも参照されたい。
ドラッグ&ドロップから取得
HTML要素にドラッグ&ドロップしたときに発生するdropイベントのイベントハンドラ内では、ドロップされたファイルのFileオブジェクトを束ねたFileListオブジェクトが取得できる。
// ドラッグ&ドロップを受け付ける要素
const dropArea = document.getElementById('drop_area');
// dropイベントのイベントハンドラ
dropArea.addEventListener('drop', function(e) {
// dataTrasnferプロパティからFileオブジェクトのリストが得られる
const fileList = e.dataTransfer.files;
for (let i = 0; fileList.length > i; i++) {
// ドロップされたファイルのFileオブジェクト
const file = fileList[i];
// ファイル名などが得られる
console.log(file.name);
…
}
…
});
ブラウザからのファイルアップロードいろいろに使用例を記載しているので、そちらも参照されたい。
ファイルの読み出し
FileReaderによる読み出し
Fileオブジェクトからその中身を読み出すには、FileReaderクラスを使う。以下の4種類の読み出し方法があり、メソッドを実行して読み出しが完了すると非同期でonloadが呼ばれて、resultプロパティから読み出し結果が取得できる。
メソッド | 説明 | 備考 |
|---|---|---|
|
|
|
|
| |
|
| Content-Typeが正しく設定されている必要がある。 |
| バイト値をそのまま文字コードとする文字列として読み出す。 | このような このメソッドの使用は推奨されていない。 |
// FileReaderクラスのインスタンスを作る
const reader = new FileReader();
// 読み出し完了時の処理(非同期)
reader.onload = function() {
// readAs~メソッドによる読み出し結果はresultプロパティで取得できる
console.log(reader.result);
};
// readAs~メソッドで読み出しを実行する
reader.readAsDataURL(file);
FileReaderは、Blobオブジェクトに対しても使用できる。
新しいメソッドによる読み出し
Promiseの仕様に対応した新しいメソッドも使用できる。text()では、readAsText()と異なり文字コードは指定できない(常にUTF-8となる)ので注意が必要である。
// FileReaderのreadAsText(file, 'UTF-8')と同じ
file.text().then(function(text) {
// textは文字列
console.log(text);
…
}
// FileReaderのreadAsArrayBuffer(file)と同じ
file.arrayBuffer().then(function(buffer) {
// bufferはArrayBufferオブジェクト
const arr = new Uint8Array(buffer);
…
});
awaitキーワードを使うと、非常にシンプルに書くことができる。
const text = await file.text(); const buffer = await file.arrayBuffer();
これらのメソッドはBlobクラスのものであるので、当然Blobオブジェクトでも使用できる。
Blob URLスキーム
Blob URLとは
blob:は新しいURLスキームで、1つのBlobオブジェクトを一時的に表すためのURLである。URLが使用できる場所ならどこでも使用できるが、人力で作成することはできず、常にJavaScriptで動的に作成する必要がある。
似たものにData URLスキーム(data:)があるが、Data URLはそのデータの情報を全てURLの中に含んでいるのに対し、Blob URLにはデータ自体は含まれておらず、そのブラウザにおけるメモリ上のBlobオブジェクトへの参照を表しているようなものとなっている。以下はData URLとBlob URLの比較である。
Data URL | Blob URL | |
|---|---|---|
URLの形式 |
|
|
URLが持つ情報 | データそのもの。 | メモリ上のデータ( |
URLの長さ | データのサイズに比例して長くなる。 | 一定の長さ。 |
生成方法・使用方法 | 静的に生成可能で、HTMLに記述したりできる。 | 常に動的に生成し、JavaScriptから使用するしかない。 |
有効期限 | 特に無し。 | ブラウザを閉じるか、明示的に破棄されるまで。 |
一意性 | 全く同じデータなら同じURLとなる。 | 同じ |
Blob URLの作成方法
Blob URLは、URL.createObjectURL()に対象のBlobオブジェクトやFileオブジェクトを渡すだけで作成できる。
// fileは、FileオブジェクトやBlobオブジェクト
const blobUrl = URL.createObjectURL(file);
// 作成したURLは、そのままHTMLのタグなどに設定できる
// 例えば、画像ファイルのFileオブジェクトであれば、以下のように<img>タグに設定して表示できる
const img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);
Blob URLを作成すると、そのページが閉じられるまでBlobオブジェクトの内容がメモリ上に置かれる。また、同じBlobオブジェクトでも、呼び出すたびに新しいURLとなる。これらのことから、必要が無くなったBlob URLは次のようにして明示的に破棄するのが望ましいとされている。
// 作成したBlob URLを破棄する URL.revokeObjectURL(blobUrl);
その他
バイナリー文字列
FileReaderのreadAsBinaryString()メソッドで登場する「バイナリー文字列」とは、\x00~\xFFの256種類の文字だけからなる文字列のことを言う。\x20~\x7Eはいわゆる通常の半角英数字であるため、半角英数字だけの文字列も「バイナリー文字列」と言えるが、通常これは人間が読む文字列に対してではなく、バイト列をそのまま文字列として扱おうとした場合に必要となってくる考え方である。実際、半角英数字の範囲以外は制御文字や記号付きのアルファベットなどであり、たいていの場合「バイナリー文字列」は文字化けしたような見た目となる。
現在ではArrayBufferなどバイト列を自由に扱えるクラスが登場したため、バイト列を文字列として取り扱う場面は少なくなっていると思われる。唯一、JavaScriptでBase64変換ができるbtoa()とatob()の2つの関数は、入出力がバイナリー文字列となっており、\xFFを超えるコードポイントの文字(通常の日本語の全角文字など)を与えるとエラーとなってしまう。
バイト値(0~255の数値)をバイナリー文字列にするには、単にString.fromCharCode()を使って1文字ずつ変換すればよい。逆に、バイナリー文字列からバイト値を得るには、charCodeAt()を使ってこちらも1文字ずつ変換すればよい。
使用例
ここまで見てきたFile APIを使うと、ユーザーが指定したファイルに対して様々な処理を施したり、新たなファイルを作成してダウンロードさせたりといったことが可能となる。
JavaScriptでファイルを作成してダウンロードする
以下は、簡単なテキストファイルをFileオブジェクトで作成して、自動的にダウンロードする例である。
// Fileオブジェクトでテキストファイルを作成
const file = new File(['Hello, world!'], 'sample.txt', { type: 'text/plain' });
// Blob URLを作成し、<a>タグにセットする
const link = document.createElement('a');
link.href = URL.createObjectURL(file);
link.download = file.name;
link.innerHTML = 'Download';
document.body.appendChild(link);
// <a>タグをクリックして自動的にダウンロードする
link.click();
テキストファイルの文字コードを判定・変換する
ユーザーが指定したテキストファイルのFileオブジェクトを受け取って、そのファイルの文字コードを判定するような関数も作れる。
※以下の例は、ASCII、EUC-JP、UTF-8のいずれか(※Shift_JISは難しいため省略した)をごく簡易的に判定するだけの実装であり、正確に判定できない場合もあるので注意されたい。より実用的には、後述するような既出のライブラリを使うのが良い。
const checkEncoding = function(file) {
// fileは、テキストファイルのFileオブジェクト
// 判定結果をもって解決するPromiseを返す
return new Promise(function(resolve, reject) {
const reader = new FileReader();
reader.onload = function() {
const bytes = reader.result;
let encoding = 'Unknown';
if (/^[\r\n\t\x20-\x7E]*$/.test(bytes)) {
encoding = 'ASCII';
} else if (/^(?:[\r\n\t\x20-\x7E]|\x8E[\xA1-\xFE]|\x8F?[\xA1-\xFE][\xA1-\xFE])+$/.test(bytes)) {
encoding = 'EUC-JP';
} else if (/^(?:[\r\n\t\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF4][\x80-\xBF]{3})+$/.test(bytes)) {
encoding = 'UTF-8';
}
resolve(encoding);
};
reader.onerror = reject;
// 正規表現で判定するため、バイナリー文字列として読み出す
reader.readAsBinaryString(file);
});
};
文字コードの変換(例えば、UTF-8からShift_JISなど)は簡単にはできないが、バイト列を自由に扱えるので原理的には可能となっている。実際、ブラウザ上で文字コード変換を行なえるライブラリもすでに登場している。例えば、https://github.com/polygonplanet/encoding.jsを使うと、以下のように文字コードの変換が簡単にできる。
const downloadSjisFile = function(file) {
// fileは、UTF-8のテキストファイルのFileオブジェクト
const reader = new FileReader();
reader.onload = function() {
// encoding.jsでShift_JISに変換したバイト列を取得
const sjisArray = Encoding.convert(new Uint8Array(reader.result), 'SJIS', 'UTF8');
// Shift_JISのテキストファイルを作成してダウンロード
const sjisFile = new File([new Uint8Array(sjisArray)], 'sjistext.txt', { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(sjisFile);
link.download = sjisFile.name;
link.innerHTML = 'Download';
document.body.appendChild(link);
link.click();
};
reader.readAsArrayBuffer(file);
});
ファイルのMD5ハッシュ値を出力する
「JavaScriptでMD5を実装してみた」の記事を参照。
外部リンク
全体
BlobとFile
FileReader
Blob URL
その他