こんにちは、太田です。今回はGoogle Chrome拡張に使えるHTML5関連技術の解説をお送りします。
Google ChromeはWebKit(Safari)と連携して、HTML5とその周辺技術の実装を積極的に進めています。一般的なウェブサイトでは、互換性の問題からHTML5などの最新実装を積極的に使用することは困難ですが、Chrome拡張ではそういった問題にとらわれずに最新の技術を試すことができます。今回から数回はそういったHTML5関連の話題を取り上げます。
Chrome拡張で使えるHTML5
HTML5は2010年1月時点で策定途中の段階です。既に一部のブラウザで実装されている仕様であっても、( 実装によるフィードバックを受けて)仕様が修正される可能性があります。多くのブラウザで実装されていて実質的に確定している部分もありますが、まだほとんど実装されていない仕様や、実装はされたものの標準化には至らない可能性のある仕様などもあります。そのため安易にHTML5を使うべきではないという点は最初に申し上げておきます。
では、実際HTML5周辺の状況はどうなっているのかという点に関しては、JavaScriptに関する部分について、2009年までのまとめ(すなわち、Chrome 4に搭載される予定の部分)を書きましたので2010年のJavaScript:「これまで」と「これから」 をご参照いただければと思います。また、本格的にHTML5を学びたい方は、Web標準とその周辺技術の学び方 を足掛かりにされるのをお薦めします。
さて、今回はHTML5のなかで、Chrome拡張で役立つCanvasとドラッグ・アンド・ドロップを取り上げたいと思います。
Chrome拡張でのCanvas
canvasはHTML5の仕様の中(正確にはCanvasの2D context APIはHTML5から分離されます)でも比較的早く実装が進んでおり、IEでもExplorerCanvas やuuCanvas.js やFlashCanvas などのライブラリを使うことで(一部機能は制限されるものの)canvas相当の機能を使うことができるため、一般的なウェブサイトでも利用されることが増えています。
ChromeではCanvasのほとんどの機能が実装されているので、画像の加工やテキストの描画など今までFlashを使うしかなかったような機能をJavaScriptで実装できるようになっています(もちろん、Flashを使うまでもない場面でCanvasを使用できるという意味であり、Flashを使うべき高度なアニメーションなどは現状のCanvasでは限界があります) 。
さて、Chrome拡張でのCanvasの利用ケースですが、なんといってもBrowser Actions API、Page Actions APIで使用するアイコンでの利用がかかせません。Browser Actions、Page Actionsで使用するアイコンにはCanvasのデータをそのまま適用することができるので、ダイナミックな(といっても19pxしかありませんが)表現が可能です。
Chrome拡張でのCanvas利用例
Canvasを使用するには、まずcanvasタグを用意します。画像をベースに使う場合は画像も読み込んでおくとよいでしょう。
HTMLでのcanvasの準備
< canvas id = "canvas" width = "19" height = "19" ></ canvas >
< img src = "sbm_icon.png" id = "icon" >
canvasタグはimgタグに似ていて、widthやheightはstyle(スタイルシート)ではなく要素に直接指定できます(スタイルシートでのサイズ指定も可能ですが、ブラウザによってサイズ指定に失敗することがあるようです) 。
では、Browser Actionsのアイコンを回転させるサンプルを見てみます。
アイコンを回転させるサンプル
window . onload = function (){
var canvas = document . getElementById ( 'canvas' );
var icon = document . getElementById ( 'icon' );
var ctx = canvas . getContext ( '2d' );
ctx . translate ( 9.5 , 9.5 );
var count = 0 ;
var rotate = function (){
ctx . clearRect (- 9.5 , - 9.5 , 19 , 19 );
ctx . rotate ( 3 * Math . PI / 180 );
ctx . drawImage ( icon , - 8 , - 8 );
chrome . browserAction . setIcon ({
imageData : ctx . getImageData ( 0 , 0 , 19 , 19 )
});
if (++ count >= 120 ) {
clearInterval ( timer );
}
};
var timer = setInterval ( rotate , 1 );
};
canvasの操作はcanvasの要素自体ではなく、その要素から取得するコンテキスト(canvas.getContext('2d')で取得するオブジェクト)を介して行います。コンテキストを取得したら、translateメソッドで中心を右下にずらしています。これは、rotateメソッドが左上を中心に回転するので、中心点を調整しないとアイコンが見切れてしまうためです。
setIntervalで回転処理を繰り返すように登録し、rotateメソッドで3度ずつ回転させて、drawImageメソッドで画像をcanvasに書き出し、getImageDataメソッドでImageDataを取得してBrowser Actionsのアイコンに反映しています。count変数で処理回数を記録し、アイコンが1周したところで終了させています。
なお、繰り返しのたびにclearRectメソッドで描画をリセットする必要があります。drawImageは前回のデータに上塗りするので余計な描画が残ってしまうためです。この点は少し注意が必要です。
なお現時点のChromeでCanvasを使用するとメモリをかなり消費します。この例のようなアニメーションを何度も繰り返すとメモリをどんどん消費していくのがタスクマネージャ(Windows標準のものではなく、Chromeに搭載されたもの)で確認できます。ある程度時間が経つとほとんどのメモリは開放されるようですが、やはりCanvasでのアニメーションは多用しないほうがよさそうです。
Chrome拡張でのドラッグ・アンド・ドロップ
ドラッグ・アンド・ドロップは従来のJavaScriptでも実現可能な機能ですが、IEの独自実装をもとにHTML5のAPIとして標準化が行われています。ドラッグ・アンド・ドロップの仕様はまだ固まっていないこともあり、Chromeでの実装も十分な状態ではありません。ですが、シンプルな使い方であれば十分に利用できますので、今回は簡単な例をいくつか紹介します。
ドラッグ・アンド・ドロップのサンプルHTML
<!DOCTYPE html>
< html >
< head >
< title > drag&drop sample </ title >
< meta charset = "utf-8" >
</ head >
< body >
< img id = "icon" src = "icon.png" draggable = "true" >
< div id = "drop" ></ div >
< script src = "drag.js" ></ script >
</ body >
</ html >
HTMLはあまり特徴がありません。一点だけ、ドラッグしたい要素にdraggableという属性をつけ、その値をtrueとすることでその要素がドラッグ可能になります。ただ、実際にはリンク(a要素)や画像(img要素)などはdraggable属性のデフォルト値がtrueになっているので、この例のようにわざわざ明示する必要は実はありません(以前のバージョンのWebKitでは、draggable属性の代わりに独自拡張としてスタイルシートでドラッグ可能な要素であることを指定する(-khtml-user-drag: element; もしくは-webkit-user-drag: element;)必要がありましたが、Chrome拡張では標準仕様のdraggable属性が使えますのでそちらを使いましょう) 。
draggable属性でドラッグはできるようになっているので、後はドロップされる側を指定します。ドロップされる側では、dragenter、dragoverのイベントにおいてpreventDefaultを呼び出す必要があります(実際には、dragenterの処理は省略しても動くようです) 。
ドラッグアンドドロップのサンプルJavaScript
var icon = document . getElementById ( 'icon' );
var drop = document . getElementById ( 'drop' );
drop . addEventListener ( 'dragenter' , function ( evt ){
evt . preventDefault ();
}, false );
drop . addEventListener ( 'dragover' , function ( evt ){
evt . preventDefault ();
}, false );
drop . addEventListener ( 'drop' , function ( evt ){
drop . appendChild ( icon . cloneNode ( false ));
evt . preventDefault ();
}, false );
ドロップ対象の要素に対して、addEventListenerメソッドで dragenter(要素にドラッグ中のマウスが載った際に発生する)イベントと、dragover(要素にマウスが載っている間に定期的に発生する)イベントをセットして、そのイベント中にpreventDefaultを呼び出しています。このpreventDefaultを呼び出した際のtarget要素(上記の例では#dropな要素)にドロップすることが可能になります。これで、シンプルなドラッグアンドドロップができました。
なお、この例ではドロップ時にはドラッグした画像をcloneしてドロップ先に置くようにしました。もしcloneせずにappendChildすれば、元の画像がドロップ先に移動することになります。
上記の例ではドロップ後の画像もさらにドラッグ・アンド・ドロップできます。ここでドラッグ可能な要素を限定する方法を考えてみます。まずシンプルな答えは、先程のdraggable属性をfalseにすることです。が、現在のChromeではこの方法ではドラッグを止めることができません。
ドロップ時に再度ドラッグできないようにする
drop . addEventListener ( 'drop' , function ( evt ){
var new_icon = icon . cloneNode ( false );
new_icon . draggable = false ;
drop . appendChild ( new_icon );
evt . preventDefault ();
}, false );
上記の方法はFirefoxでは正常に動作します。本来ならChromeでも動作するはずなのですが、現状の実装が十分でないことは先述の通りです。では、代わりの方法を考えてみます。
ドラッグ可能な要素を限定する実装例
var icon = document . getElementById ( 'icon' );
var drop = document . getElementById ( 'drop' );
icon . addEventListener ( 'dragstart' , function ( evt ){
evt . stopPropagation ();
evt . dataTransfer . setData ( 'Text' , 'original' );
}, false );
drop . addEventListener ( 'dragenter' , function ( evt ){
if ( evt . dataTransfer . getData ( 'Text' ) === 'original' ) {
evt . preventDefault ();
}
}, false );
drop . addEventListener ( 'dragover' , function ( evt ){
if ( evt . dataTransfer . getData ( 'Text' ) === 'original' ) {
evt . preventDefault ();
}
}, false );
drop . addEventListener ( 'drop' , function ( evt ){
drop . appendChild ( icon . cloneNode ( false ));
evt . preventDefault ();
}, false );
dragstartイベントで指定の画像のドラッグを開始したタイミングを捉え、dataTransferにキーワードをセットしています。dragenter、dragoverの各イベントにおいて、指定のキーワードがセットされていたらpreventDefaultを呼び出すようにしました。これで指定の画像以外ではドロップが許可されないようになりました。
なお、dataTransferを使わずとも、draggingフラグを立てて、そのフラグが立っている時のみpreventDefaultを呼び出すという方法でも実現できます。
拡張への応用
第5回 で作成したStart Tileにドラッグ・アンド・ドロップによるタブ操作を実装してみました。
図1 ドロップによるタブ操作
3つのブロックの真ん中がブックマークシートになっており、各ブックマークの手前のグリップを掴んで左のタブシートにドロップするとそのブックマークをバックグラウンドのタブで開くことができます。
基本的な処理はシンプルで、まずドラッグの開始時にdataTransferにドラッグするブックマークのURLを記録します。
ドラッグ開始時にURLをセット
bookmark_list . addEventListener ( "dragstart" , function ( evt ){
evt . stopPropagation ();
evt . dataTransfer . setData ( "URL" , evt . target . querySelector ( 'a' ). href );
}, false );
ドラッグの最中は現在どこにドロップしようとしているのか、視覚的にわかりやすくなるように背景色を変えています(マウスが乗ったり降りたりを繰り返すので、この部分の処理は少々複雑になっています) 。基本的には、dragenterイベントで背景色を設定し、dragleaveイベントで背景色を元に戻す処理です。ちなみに、Firefoxでは-moz-drag-overという独自拡張の疑似クラスがあるため、この処理を簡単に記述できるようになっています。
ドロップ時の処理
tabs_area . addEventListener ( "drop" , function ( evt ){
var href = evt . dataTransfer . getData ( "URL" );
chrome . tabs . create ({ url : href , selected : false });
}, false );
ドロップが成功した場合、dataTransferからURLを受け取って新規タブに開きます。
Start Tileのソースはbitbucket.org で確認できますので、よろしければご一読ください。
まとめ
今回はChrome拡張で使えるHTML5としてcanvasとドラッグ・アンド・ドロップを取り上げました。次回はHTML5周辺技術として、Web Storageの復習や、Web SQL database、ECMAScript 5実装などを取り上げる予定です。