File API入門 前編 File APIとFileReader APIの利用
第1回目はFile APIでできることを概観し、File APIとFileReader APIを利用し、ローカルにある画像ファイルを選択して、ブラウザでプレビューを表示するアプリを作ります。
- カテゴリー
- JavaScript >
- ブラウザAPI
発行
File APIとは
File APIでできることを簡潔に言うと、ローカルマシンに存在するファイルをJavaScriptから操作することです。たとえば、次のような実装が可能です。
- ローカルにある画像をアップロードする前にプレビューを表示する
- ローカルにある画像をブラウザで加工してダウンロードする
- テキストファイルをブラウザで解析する
- データをJSONファイルとしてエクスポートして、インポートする
- 大きなファイルを分割してサーバーに送信する
当シリーズでは2回に分けて、1回目はFile APIとFileReader APIを利用し、ローカルにあるファイルを選択して、ブラウザでプレビューを表示する簡単なアプリを書くことをゴールとします。
次回、2回目ではBlob constructingとBlob URLsを利用して、なんらかのデータをダウンロードするようなアプリを書くことをゴールとします*。
*注:シリーズでは触れない仕様
ローカルのサンドボックス化されたディレクトリを操作できるFileSystem API、ファイルの書き込みが行えるFileWriter APIについては、本シリーズでは触れません。
本シリーズで扱うAPIの対応ブラウザとバージョン
まずは扱うAPIに対応しているブラウザとそのバージョンを見てみましょう*。
*注:サポートの詳細
この表はCan I use...を参照しています。また以下のような方針で作成されています。
- Partialサポートは除く
- Blob constructingは、deprecatedになったBlobBuilder APIを除く
AndroidブラウザではBlob constructingがサポートされていません。
API | Chrome | Firefox | Safari | Opera | IE | iOS | Android |
---|---|---|---|---|---|---|---|
File API | 13+ | 3.6+ | 6+ | 11.1+ | 10+ | 6+ | 3+ |
FileReader API | 13+ | 3.6+ | 6+ | 11.1+ | 10+ | 6+ | 3+ |
Blob constructing | 20+ | 13+ | 6+ | 12.1+ | 10+ | 6+ | -- |
Blob URLs | 8+ | 4+ | 6+ | 15+ | 10+ | 6+ | 4+ |
それでは、今回扱うFile APIとFileReader APIの概要とサンプルを見ていきましょう。
File APIの概要:FileListとFile
単一のファイルを扱うFile、Fileをオブジェクトとして持つFileListがあります。
input[type="file"]
に渡したファイル(FileList)をfilesプロパティで参照できます。またinput[type="file"]
要素にはmultiple
属性を付けることで、複数のファイルを選択できるようにできます。
FileList
は配列ではなくオブジェクトですので、注意してください。
ソースコードは次のようになっています。
<input type="file" id="file">
var inputFile = document.getElementById('file');
function fileChange(ev) {
var target = ev.target;
var files = target.files;
console.log(files);
}
inputFile.addEventListener('change', fileChange, false);
なんでもいいのでJPEG画像ファイルを選択してみましょう。コンソールにはオブジェクトが表示されます。
FileList { 0: File, length: 1, item: function }
// FileListを展開
{
0: File
length: 1
}
// Fileを展開
{
0: {
lastModifiedDate: Sat Jun 22 2013 23:45 GMT+0900 (JST),
name: "image.jpeg",
size: 33792,
type: "image/jpeg"
},
length: 1
}
このように、FileList
オブジェクトはFile
オブジェクト群を内包しています。
FileList
オブジェクトには、length
プロパティがあり、File
オブジェクトにはインデックスのようなキーが割り振られていますが、FileList
オブジェクトは配列ではありません。
Fileオブジェクトのプロパティ
File
オブジェクトは選択したファイルの情報を内包しています。
プロパティ | 解説 |
---|---|
lastModifiedDate | ファイルの最終更新日(Dateオブジェクト) |
name | ファイル名 |
size | ファイルのサイズ(byte) |
type | ファイルのMIMEタイプ |
File
オブジェクトにはtype
プロパティや、size
プロパティがありますので、MIMEタイプやファイル容量をフロント側で制限することも可能です。
10KB以下のJPEG画像ファイルしか選択できないサンプルを用意しました。
var inputFile = document.getElementById('file');
function fileChange(ev) {
var target = ev.target;
var file = target.files[0];
var type = file.type; // MIMEタイプ
var size = file.size; // ファイル容量(byte)
var limit = 10000; // byte, 10KB
// MIMEタイプの判定
if ( type !== 'image/jpeg' ) {
alert('選択できるファイルは10KB以下のJPEG画像だけです。');
inputFile.value = '';
return;
}
// サイズの判定
if ( limit < size ) {
alert('10KBを超えています。10KB以下のファイルを選択してください。');
inputFile.value = '';
}
}
inputFile.addEventListener('change', fileChange, false);
FileReader API
FileReader
では、Fileオブジェクトのファイルを実際に読み込みます。プレビューとして表示するというような動作はFileReader
を利用して行います。
まずはコンストラクタからインスタンスを作ります。
var reader = new FileReader();
FileReaderの読み込みメソッド
そしてFileReader
には非同期な3つの読み込みメソッドが定義されていますので、いずれかを使ってファイルを読み込みます。
メソッド | 引数 | 解説 |
---|---|---|
readAsArrayBuffer | Blob or File | ファイルをArrayBufferとして読み込む |
readAsText | Blob or File | ファイルをテキストとして読み込む |
readAsDataURL | Blob or File | ファイルをDataURLとして読み込む |
また、W3Cの仕様書には記載されていませんが、次の読み込みメソッドも利用できます。
メソッド | 引数 | 解説 |
---|---|---|
readAsBinaryString | Blob or File | ファイルをバイナリ形式で読み込む |
次のようなコードになります。
reader.readAsDataURL(file);
FileReaderのその他のメソッドとプロパティ
読み込みメソッド以外には、次のようなメソッドがあります。
メソッド | 解説 |
---|---|
abort | 読み込みを破棄する |
また、次のようなプロパティがあります。
プロパティ | 解説 |
---|---|
state | 読み込みステータス |
result | 読み込み結果 |
result
はreadAs~
を実行した後、次に解説するonload
イベントのタイミングで取得できるようになります。
FileReaderのイベント
readAs~
でファイルの読み込みを実行または、読み込みをabort
すると、FileReader
のイベントが発行されます。
イベント | 解説 |
---|---|
onloadstart | 読み込みを開始した |
onprogress | 読み込み中 |
onabort | 読み込みを破棄した |
onerror | エラーが発生した |
onload | 読み込みが成功して完了した |
onloadend | 読み込みが完了した(エラーを含む) |
読み込み完了時にデータを表示する
とても簡単に書けます。HTMLは最初のものと同じです。
var inputFile = document.getElementById('file');
var reader = new FileReader();
function fileChange(ev) {
var target = ev.target;
var file = target.files[0];
var type = file.type;
var size = file.size;
if ( type !== 'image/jpeg' ) {
alert('選択できるファイルはJPEG画像だけです。');
inputFile.value = '';
return;
}
reader.readAsDataURL(file);
}
function fileLoad() {
console.log(reader.result);
}
inputFile.addEventListener('change', fileChange, false);
reader.addEventListener('load', fileLoad, false);
コンソールを見ると、次のような出力が確認できます。
=> console(画像を選択すると表示されます。長すぎるので省略しています)
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEA...
お気づきかと思いますが、readAsDataURL
で読み込んだ画像は、img要素のsrcに入れて、そのまま表示することができます。
プレビューを表示するアプリを書いてみる
それでは、準備もできたので、選択したファイル(複数可)の名前とファイルサイズ、プレビューを表示するような、簡単なアプリを書いてみましょう。
ファイルは複数選択できる仕様です。ファイル選択ダイアログが開いたら、command(ctrl)+クリックで読み込みたい画像を選択できます。複数ファイルを指定すると、指定したファイル数が表示されます。
なお、アプリではjQueryとUnderscore.js、Backbone.jsを利用します。Underscore.jsとBackbone.jsがよくわからない場合は、過去のシリーズと連載をご覧ください。
PreviewImg:HTMLはシンプル
まずHTMLですが、UIはすべてJavaScriptから吐き出すので、ベースになるHTMLはとてもシンプルです。
<div class="mod-previewImg"></div>
PreviewImg:Modelを書く
Modelもとてもシンプルに書けます。
file attribute
にFile
オブジェクトを持たせます。Viewから参照できるように、dataURL
にはFileReaderでの読み込みが完了した際に、URLを保存します。
また、このModelはFileReader
も個別に扱います。readFile
メソッドを持たせて、ここではFile
オブジェクトをdataURLとして読み込みます。FileReader
のload
イベント完了を監視し、読み込みが完了したらイベントを発行するような仕組みです。
// Fileオブジェクトを内包するモデル
var FileModel = Backbone.Model.extend({
defaults: {
file: '',
dataURL: ''
},
initialize: function(attr) {
var self = this;
// FileReaderを準備しておく
self.reader = new FileReader();
self._eventify();
},
_eventify: function() {
var self = this;
// ファイルの読み込みが完了したらdataURLに値をセット
// その後、readerLoadイベントを発行する
self.reader.addEventListener('load', function() {
self.set('dataURL', self.reader.result);
self.trigger('readerLoad', self);
}, false);
},
// ファイルを読み込むメソッド
readFile: function() {
var self = this;
var reader = self.reader;
reader.readAsDataURL(this.get('file'));
}
});
PreviewImg:Collectionを書く
Collectionはさらにシンプルです。
各モデルのreadFile
メソッドを呼ぶためのreadFiles
メソッドを持っているだけです。つまり、ViewからはCollectionのreadFiles
メソッドを呼ぶだけでいいというわけです。
Modelが発行したreaderLoad
イベントはCollectionでもキャッチできるので、ViewからはこのCollectionのイベントを監視するようにすればいいだけです。
// FileListを元にしたコレクション
var FileCollection = Backbone.Collection.extend({
model: FileModel,
// それぞれのModelのreadFileコールする
readFiles: function() {
var self = this;
this.each(function(file) {
file.readFile();
});
}
});
PreviewImg:Viewを書く
Viewは、ModelとCollectionに比べると、少し長くなりますが、分けて考えれば、わかりやすいです。大きく分けて、行っていることは以下の2つです。
初期化
Viewの役割としては、初期化時に、以下のことを行います。
- Collectionを初期化する
input[type="file"]
や決定ボタン、プレビューエリアの枠をレンダリングする- Collectionが発行するイベントを監視しておく
ファイルの選択と、プレビューボタンのクリック
イベントデリゲーションで、各要素に動作を決めます。
input[type="file"]
の選択をしたら、FilesからCollectionをresetする- 決定ボタンがクリックされたら、Collectionからプレビューエリアをレンダリングする
実際のコードを見てみましょう。
// UI部分
var FileView = Backbone.View.extend({
// イベントデリゲーション
events: {
'change .file' : '_onFileChange',
'click .submit' : '_onClickButton'
},
initialize: function($input, $preview) {
var self = this;
// Collectionを初期化しておく
self.collection = new FileCollection();
// UIをレンダリングする
self.render();
self._eventify();
},
_eventify: function() {
var self = this;
// Modelが発行するイベントをキャッチして
// プレビューエリア内をレンダリングする
self.collection.on('readerLoad', self.renderPreview, self);
},
// UIをレンダリングするメソッド
render: function() {
var self = this;
var $el = self.$el;
$el.append(self.html);
self.$input = $el.find('.file');
self.$preview = $el.find('.preview');
},
// プレビューエリア内をレンダリングするメソッド
renderPreview: function(model) {
var self = this;
self.$preview.append(
_.template(
self.previewHtml,
{ model: model }
)
);
},
// UI部分のテンプレート
html: [
'<p>選択できるファイルはJPEG画像ファイルです。<br>',
'<input type="file" class="file" multiple></p>',
'<p><input type="button" class="submit" value="選択したファイルのプレビューを表示する"></p>',
'<div class="preview"></div>'
].join(''),
// プレビューエリアに追加するテンプレート
previewHtml: [
'<p>',
'<img src="<%= model.get(\'dataURL\') %>"><br>',
'<span class="name">name: <%= model.get(\'file\')[\'name\'] %></span><br>',
'<span class="size">size: <%= model.get(\'file\')[\'size\'] %>(byte)</span>',
'</p>'
].join(''),
// Filesオブジェクトを扱いやすいように作り直す
// ModelにはFileオブジェクトを直接渡せないので、
// ひとつ下げて持たせておく
_getFiles: function(files) {
var self = this;
var ret = [];
_.each(files, function(file, key) {
if ( !files.hasOwnProperty(key) || key === 'length' ) {
return;
}
// fileのMIMEタイプはimage/jpegだけを許可する
// それ以外は無視する
if ( file.type !== 'image/jpeg' ) {
return;
}
// ひとつ下げる
ret.push({ file: file });
});
return ret;
},
// 配列filesからCollectionを作る
_getCollection: function(files) {
var self = this;
self.collection.reset(files);
},
// input[type="file"]でファイルが選択されたとき
_onFileChange: function(ev) {
var self = this;
var target = ev.target;
var files = self._getFiles(target.files);
self._getCollection(files);
},
// プレビューエリアを表示するボタンをクリックしたとき
_onClickButton: function() {
var self = this;
// プレビューエリアを空にする
self.$preview.empty();
// Collectionに何もない場合はアラートを出す
if ( !self.collection || !self.collection.length ) {
return alert('先にファイルを選択してください');
}
// 問題なければCollectionのreadFilesメソッドをコールする
self.collection.readFiles();
}
});
まとめ
サンプルを通して、File API、FileReader APIについて理解していただけたでしょうか。次回紹介する、Blob constructingとBlob URLsを利用すると送信するデータのスライスや、データのダウンロードまでが可能になり、さらにFile APIの幅が広がるでしょう。