はじめに
HTML5とそれに関連するAPIの普及により、「Webのアプリケーションプラットフォーム化」への流れが大きく進んでいます。
Internet Explorer 10(以下、IE10)では、そうしたHTML5と関連APIの数多くがサポートされています。今回の記事では、IE10が新たにサポートするFile APIを中心に解説していきます。
File APIとは、今までJavaScriptが直接扱うことが出来なかったローカルファイルに対して、読み取り操作を実現可能にするものです。また、今後の展開としてローカルファイルの書き込みや保存、さらにはWebアプリに専用のファイルシステムをサポートするAPIも、W3Cで標準化作業が進行中です。
書き込みについてはFile API: Writerで、ファイルシステムについてはFile API: Directories and Systemという仕様が対応しています。
余談ですが、IE10ではFile APIだけではなく「BlobBuilder」という、File API: Writerに関する一部の機能もサポートされています。こちらのトピックについては、「BlobBuilder を使ってファイルを作成する」という記事に詳しく解説があります。
今回は、File APIを解説するための題材として、「MOGUTARO eats files!」というデモンストレーションを取り上げます。このデモはIE Test Drive向けに我々が開発したもので、2012年2月16日に開催されたDevelopers Summit 2012においても、「次期Internet Explorer、IE10とHTML5 API」というセッションで紹介されました。
デモの説明
このデモンストレーションは、ファイルを任意の要素(くじらの口)にドラッグ&ドロップすることでファイルを読み込むことができ、そのファイル形式によって異なる様々な動作をします。
対象となるファイル形式と動作は以下の4つです。
- テキストファイル:1行ごとにテキストファイルを読み上げる
- 画像ファイル:画像の色を解析をして虹を表示後、canvas上に表示する
- 動画ファイル:動画再生する
- 音楽ファイル:音楽再生する
このデモンストレーションのソースコードは、こちらからダウンロードすることができます。
以下、掲載するコードはjQueryを利用しています。アニメーションについてはTween.jsをというJavaScriptライブラリを利用していますが本記事ではこちらについての説明は割愛します。
ドラッグ&ドロップ
File APIを使って任意のローカルファイルを読むためには、ユーザが明示的にWebアプリケーションに対してファイルを「読み込ませる」というアクションが必要になります。
現在それらのアクションは、ファイル選択フォーム(<input type="file">)を用いるか、ドラッグ&ドロップを受け付けるか、の2つに限られています。このデモンストレーションでは、くじら(MOGUTARO)の大きく開いた口に対して、ファイルをドラッグ&ドロップすることができます。
以下に示すのは、ファイルをドラッグ&ドロップによって受け取るためのコードを簡略化したものです。
dragenter・dragover・dropのイベントを制御することで実現しています。dragenter、dragoverでは、デフォルトのイベント処理をキャンセルしています。これは、デフォルトではブラウザはドラッグ&ドロップを受け付け「ない」ため、そのデフォルト操作をキャンセルする必要があるためです。ondropイベントの処理において、ファイルの読み込みとそれに続く処理を実現しています。
$("#mouthOfWhale") .on("dragenter", function(e) { // dragenter時 の ネイティブイベント無効化 e.preventDefault(); }) .on("dragover", function(e) { // dragover時 の ネイティブイベント無効化 e.preventDefault(); }) .on("drop", function(e) { // drop時 の イベントのバブリング無効化 e.stopPropagation(); /* ondropイベントのコールバック ファイル形式の振り分け */ });
ファイル形式の振り分け
ドラッグ&ドロップされたファイルはondropイベントから取得できます。さらに取得したファイルからファイルタイプを取得します。ファイル形式の振り分けには、このファイルタイプから判定、処理分岐させています。
なお、jQueryを使用しているため、e.originalEvent.dataTransfer.filesから取得しています(jQueryは、独自のイベントオブジェクトでオリジナルのイベントをラップするためです)が、ネイティブのコードの場合は、e.dataTransfer.filesからの取得となるのでご注意ください。
// e.originalEvent.dataTransfer.files に対象のファイル(FileList)がセットされる var files = e.originalEvent.dataTransfer.files; // FileListからFileを取得する var file = files[0] || null; // ファイルタイプを取得する var fileType = (file && file.type)? file.type: ''; // テキストファイル判定 if (fileType.indexOf('text/') === 0) { processTextFile(file); } // 画像ファイル判定 else if (fileType.indexOf('image/') === 0) { processImageFile(file); } // 動画ファイル判定 else if (fileType.indexOf('video/') === 0 && /probably|maybe/.test($('#tv')[0].canPlayType(fileType))) { processVideoFile(file); } // 音楽ファイル判定 else if (fileType.indexOf('audio/') === 0 && /probably|maybe/.test($('#audioPlayer')[0].canPlayType(fileType))) { processAudioFile(file); } // etc file else { processOtherFile(file); }
canPlayType(type)による判定
上のコードで使用しているcanPlayType(type)は、各ブラウザがそのメディアファイルを再生することができるかどうかを判定します。再生可能な場合は「probably」、再生できると確証を得られない場合は、「maybe」、再生不可の場合は「''」がリターンされます。
メディア要素を扱う際には、ブラウザがどのフォーマットのファイルをを再生できるかというのも注意が必要です。IE10の場合、音楽ファイルであれば MP3 や AAC、動画ファイルであれば H.264形式でエンコードされたMPEGファイルが再生可能です。
FileReaderの機能
FileReaderとは、名前の通りファイルを読み込むためのAPIで、以下のメソッドを利用できます。
- readAsArrayBuffer(file)・・・ファイルを読み取った結果をArrayBuffer(型付き配列)の形式で保持します。
- readAsBinaryString(file)・・・ファイルを読み取った結果をString型で保持します。ファイルから読み取った内容(バイト配列)を、Stringのインターフェースを通じてアクセス可能にしているだけなので、結果をテキストとして扱うことはできません。
- readAsText(file, encode)・・・テキストファイルを読み取った結果をString型で保持します。ファイルの文字コードを指定することができます。
- readAsDataURL(file)・・・ファイルを読み取った結果をData URL形式の文字列で保持します。
- abort()・・・ファイルの読み込み処理を中断します。
このサンプルでは、テキストファイルを読み込む際に readAsText(file, encode)、画像ファイルを読み込む際にはreadAsDataURL(file) を利用しています。
FileReaderのreadAs...()メソッドは非同期で実行されます。実行結果はFileReader.resultというプロパティに各メソッドの形式でセットされます。
以下、テキストファイルを読み込む際のコードです。reader.result にはテキストファイル形式でデータがセットされます。
var reader = new FileReader(); reader.onload = function() { // reader.result にテキスト形式でデータでセットされている var result = reader.result; // 1行ずつ読み上げる } reader.readAsText(file, 'UTF-8');
次は、画像ファイルの読み込みです。
var reader = new FileReader(); reader.onload = function() { var result = reader.result; // 画像の色を解析をして虹を表示後、canvas上に表示する } reader.readAsText(file, encode);
記述の仕方自体は同じですが、reader.resultにセットされるファイル形式が[Data URL]になります。そのため、 tmpImg.src = reader.result のように受け渡すことができるようになり、画像の加工やcanvasへの反映を実現可能にしています。
var reader = new FileReader(); reader.onload = function() { var result = reader.result; // 画像の色を解析をして虹を表示後、canvas上に表示する var tmpImg = document.createElement('img'); tmpImg.src = reader.result; tmpImg.onload = function() { // 読み込んだ画像を加工して、canvas上に表示する var drawCanvas = $('#pict'); var canvasWidth = drawCanvas.attr('width'), canvasHeight = drawCanvas.attr('height'); var canvasProp = calcCanvasPosition(tmpImg.naturalWidth, tmpImg.naturalHeight, canvasWidth, canvasHeight); var pictCtx = drawCanvas[0].getContext('2d'); pictCtx.clearRect(0, 0, canvasWidth, canvasHeight); ・・・ } } reader.readAsDataURL(file);
window.URL.createObjectURL(file)
FileReader.readAsDataURL()は対象データを全てメモリ上に保持するため、メディアファイルのようにファイルサイズが大きいものには向いていません。ファイルの内容を全てメモリに読み込むことなく、ローカルのメディアデータをvideo要素やaudio要素、img要素を使用して処理するには、URL.createObjectURL()というメソッドが役に立ちます。
URL.createObjectURL()は、引数として渡されたファイルに対する参照を表すURL文字列を生成するメソッドです(実際はファイルに限らず、Blobと呼ばれるオブジェクトの全てに対してこのメソッドを呼び出すことができます)。
生成されるURLは、Blob URIという形式に則り、「blob:」で始まる短い文字列となります。
下記のコードは音楽ファイルの再生箇所の抜粋です。
function processAudioFile(file) { var audio = $('#audioPlayer')[0]; var audioData = URL.createObjectURL(file); // 音楽ファイルURLのセット audio.src = audioData; // 音楽再生 audio.play(); }
動画ファイルも同様に扱えます。こちらは再生ボタン・停止ボタンのイベントを登録しています。
function processVideoFile(file) { var tv = $('#tv'); var videoData = URL.createObjectURL(file); // 動画ファイルURLのセット tv.attr('src', videoData).show(); pauseFunc = function() { tv[0].pause(); }; // リモコンの再生ボタンで動画再生 $('#moviePlay').on('click', function(){ tv[0].play(); }); // リモコンの停止ボタンで動画停止 $('#movieStop').on('click', function(){ pauseFunc(); }); // 動画終了時 tv.on('ended', function(){ pauseFunc(); }); }
まとめ
File APIにより、「JavaScriptでファイルの内容を読み取る」というこれまで不可能とされてきた事柄を容易に実現できるようになりました。この一点だけでも、Webアプリケーションでの大きなイノベーションの予感を感じさせるAPIだといっても過言ではないでしょう。
また、冒頭でもお伝えしましたが、今後はローカルファイルへの書き出しやファイルシステムの取り扱いも視野に入れて仕様が検討されています。これらのAPIが実装された暁には、デスクトップアプリケーションと比べても機能的に全く劣ることがないような、さらに高度なWebアプリケーションの実現が期待できると言って良いでしょう。