HTML5は最近のWebにおける中心的な存在です。そして今日のインタラクティブな画像作成においては、SVGとCanvasが多くの人が好んで使う技術になっています。Flashは忘れ去られ、SilverlightはWebのどこかに生息する希少動物のユニコーンのようになり、サードパーティー製プラグインのことを覚えている人はほとんど見かけなくなりました。
SVGとCanvasについてそれぞれの長所も短所もよく議論されていますが、インタラクティブ要素を作成したり操作したりする場合にはSVGがより適しています。SVGはXML言語ベースのベクター形式であるため<svg>タグを使って画像をページに読み込めばすべてのエレメントがSVG DOMで利用可能になるからです。
今回の記事では、オープンソースのJavaScript描画ライブラリー、GraphicsJSを紹介します。GraphicsJSは最近誕生した優秀なライブラリーで、SVGをベースにしています(IEの古いバージョンではVMLで代用)。
この記事ではまずGraphicsJSの基本について手短に説明したあと、2つの簡潔ですがスペクタクルなサンプルを使ってライブラリーの機能を披露します。1つ目のサンプルは画像の描画そのものについてですが、2つ目のサンプルでは画像を使ったシンプルな暇つぶし用ゲームを50行以下のコードで作成します。
GraphicsJSを使う理由
SVGを扱うために開発者が利用できるライブラリーはたくさんあります。Raphaël、Snap.svg、BonsaiJSは数ある優秀なライブラリーのほんの数例にすぎません。これらライブラリーにはそれぞれに長所と短所がありますが、比較は別の記事に任せることにして、GraphicsJSの特長を紹介します。
第1に、GraphicsJSは軽量でとても柔軟性の高いJavaScript APIです。豊富なリッチテキスト機能に加え、仮想DOMも実装しており、HTML DOMのようにブラウザーに合わせて実装を変える必要がありません。
第2に、GraphicsJSはインタラクティブ・データビジュアライゼーション分野で世界のソフトウェア開発を牽引するAnyChartが、つい最近(昨年の秋に)公開したオープンソースの新たなJavaScriptライブラリーです。AnyChartは独自開発しているグラフ描画製品にGraphicsJSを3年以上(AnyChart 7.0のリリース以降)使用しており、GraphicsJSは十分な実践経験があります(編注:筆者はAnyChartの研究開発リーダーで、GraphicsJSの主任開発者です)。
第3に、AnyChartのほかのグラフ作成用JavaScriptライブラリーとは違い、GraphicsJSは商用、非商用プロジェクトのどちらにも利用できます。GraphicsJSはApacheライセンスのもとに、GitHubで公開されています。
第4に、GraphicsJSはクロスブラウザー対応で、Internet Explorer 6.0以上、Safari 3.0以上、Firefox 3.0以上、Opera 9.5以上をサポートしています。IEの古いバージョンではVMLを描画し、そのほかのすべてのブラウザーではSVGを描画します。
最後に、GraphicsJSを使用して画像とアニメーションを組み合わせれば、すばらしいエフェクトを作成できます。GraphicsJSのメインギャラリーでは、たき火のアニメーション、回転する銀河、雨が降る様子、描画処理による葉の生成、実際に遊べる15パズルなどが掲載されています。GraphicsJSの詳細なドキュメントや、包括的なAPIリファレンスにはさらに多くの例が載っています。
GraphicsJSの基本
GraphicsJSを使い始めるには、ライブラリーの参照とHTMLのブロックレベル要素の作成が必要になります。
<html lang="en">
<head>
<meta charset="utf-8" />
<title>GraphicsJS Basic Example</title>
</head>
<body>
<div id="stage-container" style="width: 400px; height: 375px;"></div>
<script src="https://cdn.anychart.com/js/latest/graphics.min.js"></script>
<script>
// GraphicsJS code here
</script>
</body>
</html>
次にステージ(stage)を作成し、その中に長方形や円、あるいはほかの図形を描画します。
// create a stage
var stage = acgraph.create('stage-container');
// draw a rectangle
var stage.rect(25, 50, 350, 300);
CodePenに例があります。例では少し複雑な死の秘宝のシンボルを描画しています。
1つ目の作品
塗りつぶし、ストローク、パターンによる塗りつぶし
どのような図形や線でも塗りつぶし設定やストローク(境界)設定を使って色を設定できます。ストロークはあらゆるものにありますが、塗りつぶしは図形と閉じた線にしかありません。塗りつぶし設定、ストローク設定は豊富な機能を持っており、塗りつぶし設定とストローク設定の両方で線形および放射状のグラデーションを使用できるほか、破線も書け、さまざまなタイリングモードでの画像による塗りつぶしもサポートしています。
これらの機能はどのようなライブラリーにも備わっている標準的なものですが、GraphicsJSが優れている点は網掛けやパターンによる塗りつぶしの機能です。この機能では、32種類(!)のすぐに使える網掛けやパターンが利用できるだけでなく、図形やテキストで構成する独自のパターンも簡単に作成できます。
それでは実際に、GraphicsJSができることを説明していきます。1人の男性が家の近くに立っている小さな絵を描画し、さまざまなパターンや色で塗りつぶして彩色します。話を簡単にするために、ナイーヴ・アートの絵を作成します(アウトサイダー・アートにのめり込むのはやめましょう)。では始めます。
// create a stage
var stage = acgraph.create('stage-container');
// draw the frame
var frame = stage.rect(25, 50, 350, 300);
// draw the house
var walls = stage.rect(50, 250, 200, 100);
var roof = stage.path()
.moveTo(50, 250)
.lineTo(150, 180)
.lineTo(250, 250)
.close();
// draw a man
var head = stage.circle(330, 280, 10);
var neck = stage.path().moveTo(330, 290).lineTo(330, 300);
var kilt = stage.triangleUp(330, 320, 20);
var rightLeg = stage.path().moveTo(320, 330).lineTo(320, 340);
var leftLeg = stage.path().moveTo(340, 330).lineTo(340, 340);
CodePenで結果を確認できます。
上のように、さまざまな変数を使用しています。ステージでなにかを描画するメソッドはすべて、作成したオブジェクトへの参照を返します。このリンクはオブジェクトを置き換えたり、削除したりする際に使います。
また、メソッドチェーン(GraphicsJSの至る所で使用されています)によってコードを短くできている点にも注目してください。メソッドチェーン(例:stage.path().moveTo(320, 330).lineTo(320, 340);)は慎重に使用すべきですが、適切な使い方をすればよりコンパクトで簡単なコードが書けます。
この色塗りのページは子供に渡して、代わりに色を塗ってもらっても良いでしょう。それくらい、以下のテクニックを習得するのは簡単なことです。
// color the picture
// fancy frame
frame.stroke(["red", "green", "blue"], 2, "2 2 2");
// brick walls
walls.fill(acgraph.hatchFill('horizontalbrick'));
// straw roof
roof.fill("#e4d96f");
// plaid kilt
kilt.fill(acgraph.hatchFill('plaid'));
サンプルはここで確認できます。
これで、キルトを身にまとったハイランド人が、レンガとわら葺屋根で作られた城の近くにたたずむ絵ができました。この絵は1つの芸術作品と言って良いほどのできで、著作権は私たちにあると主張しても差し支えありません。そこで、独自のテキストベースの塗りつぶしパターンを使用して著作権を主張したいと思います。
// 169 is a char code of the copyright symbol
var text = acgraph.text().text(String.fromCharCode(169)).opacity(0.2);
var pattern_font = stage.pattern(text.getBounds());
pattern_font.addChild(text);
// fill the whole image with the pattern
frame.fill(pattern_font);
以上のように、とても簡単です。テキストオブジェクトのインスタンスを生成し、ステージでパターンを作成したあとに、テキストを作成したパターンに入れるだけです。
画像を使った暇つぶし用のゲームを50行以下のコードで作成
次に、GraphicsJSを使ってクッキークリッカー型のゲームを50行以下のコードで作成する方法を紹介します。
ゲームの名前は「Street Sweeper in the Wind(風の中の道路掃除)」で、プレイヤーは風の吹くある秋の日の午後に、町の道路をきれいにする道路掃除の役割を担います。ゲームではGraphicsJSギャラリーのサンプルにある描画処理による葉の生成のコードの一部を使用しています。
完成したゲームはCodePenで(または記事の最後で)確認できます。
Layers、zIndex、仮想DOM
前回と同じように、ステージの作成から始めます。変数をいくつか定義しておきます。
// create stage
var stage = acgraph.create("stage-container");
// color palettes for leaves
var palette_fill = ['#5f8c3f', '#cb9226', '#515523', '#f2ad33', '#8b0f01'];
var palette_stroke = ['#43622c', '#8e661b', '#393b19', '#a97924', '#610b01'];
// counter
var leavesCounter = 0;
このゲームでは、GraphicsJSの要素をグループ化するためのオブジェクト、Layer(レイヤー)を使用します。要素を変形するなどの操作を複数の要素に対して適用する場合は、要素をグループ化する必要があります。レイヤーの変更にはサスペンドモード(詳しくはあとで説明します)を使用しますが、パフォーマンスとユーザーエクスペリエンスの向上にも寄与しています。
デモでは、レイヤー機能を使用して「葉」をグループ化すると同時に、葉がラベル(掃除した葉の枚数を表示)を覆ってしまわないようにします。ラベルを作成し、続いてstage.layerメソッドを呼び出し、ステージに結びつけられたレイヤーを作成します。このレイヤーのzIndexプロパティに対し、ラベルよりも表示順位の低い値を割り当てます。
// create a label to count leaves
var counterLabel = stage.text(10,10, "Swiped: 0", {fontSize: 20});
// a layer for the leaves
var gameLayer = stage.layer().zIndex(counterLabel.zIndex()-1);
以上により、レイヤーに作成した葉の枚数によらず、テキストが常に見えるようになります。
画像を変形する
次に、葉を描画する関数を追加します。GraphicsJSには画像を変形するための便利なAPIがあり、このAPIを利用して要素および要素グループを移動、拡大縮小、回転、削除できます。このAPIをレイヤーや仮想DOMと併用すれば、とても強力なツールになります。
function drawLeaf(x, y) {
// choose a random color from a palette
var index = Math.floor(Math.random() * 5);
var fill = palette_fill[index];
var stroke = palette_stroke[index];
// generate random scaling factor and rotation angle
var scale = Math.round(Math.random() * 30) / 10 + 1;
var angle = Math.round(Math.random() * 360 * 100) / 100;
// create a new path (leaf)
var path = acgraph.path();
// color and draw a leaf
path.fill(fill).stroke(stroke, 1, 'none', 'round', 'round');
var size = 18;
path.moveTo(x, y)
.curveTo(x + size / 2, y - size / 2, x + 3 * size / 4, y + size / 4, x + size, y)
.curveTo(x + 3 * size / 4, y + size / 3, x + size / 3, y + size / 3, x, y);
// apply random transformations
path.scale(scale, scale, x, y).rotate(angle, x, y);
return path;
};
すべての線(path)を同じ方法で作成していますが、作成後に変形を加えることで、葉のパターンをランダムに作成できます。
イベント処理
ステージやレイヤーなど、GraphicsJSのあらゆるオブジェクトはイベントを処理できます。サポートされているイベント一覧はEventType APIで確認できます。ステージには描画をコントロールするための4つの専用イベントがあります。
このゲームでは葉オブジェクトに設置したイベントリスナーを使用し、ユーザーが葉にマウスオーバーした際に葉を1つ1つ非表示にしていきます。以下のコードをdrawLeaves関数の最後にあるreturn文の前に追加します。
path.listen("mouseover", function(){
path.remove();
counterLabel.text("Swiped: " + leavesCounter++);
if (gameLayer.numChildren() < 200) shakeTree(300);
});
またここでは葉の枚数を数えるためにレイヤーを使っています。
if (gameLayer.numChildren() < 200) shakeTree(300);
例では実際の葉の枚数を格納しているわけではないことに注意してください。特定のレイヤーに追加したりレイヤーから削除したりする線の数が葉の枚数なので、どれだけの子があるか(つまり、どれだけの葉が残っているか)が数えられるのです。
GraphicsJSはHTML DOMを抽象化した仮想DOMを提供し、軽量で、ブラウザーによってSVGとVMLの実装を使い分ける必要がありません。すべてのオブジェクトやレイヤーを管理し、グループ単位で画像の変形を適用できるなど、便利な機能が数多くあります。さらに、描画プロセスを管理するメソッドを使えば描画の最適化もできます。
パフォーマンスの最適化
GraphicsJSでは、仮想DOMとイベントハンドラを組み合わせることで描画コントロールを可能にしています。パフォーマンスの項目にはどのように関連しているか書かれています。
ゲームで葉を生成する際は、新たな葉を追加する間は描画を中断し、すべての変更が完了してから描画を再開する必要があります。
function shakeTree(n){
stage.suspend(); // suspend rendering
for (var i = 0; i < n; i++) {
var x = Math.random() * stage.width()/2 + 50;
var y = Math.random() * stage.height()/2 + 50;
gameLayer.addChild(drawLeaf(x, y)); // add a leaf
}
stage.resume(); // resume rendering
}
この方法で新たな要素を処理することにより、新たな葉がほぼ一瞬で表示されます。
最後にshakeTree()を呼び出してすべての処理を開始します。
// shake a tree for the first time
shakeTree(500);
最終結果
最後に
HTML5に移行したことでWebは変わりました。最新のWebアプリケーションでも、シンプルなWebサイトであっても、画像の操作が必要になることは良くあります。どのような状況でも、うまくいく解決策を見つけられないにしても、GraphicsJSライブラリーは検討してみるべきです。GraphicsJSはオープンソースであり、堅牢で、ブラウザーを強力にサポートします。GraphicsJSに備わっている数多くの機能は使っていて楽しく、便利で、そしてもちろん、役立つ機能です。
さらに詳しく知りたい人のためのリンク
- 概説
- ライブラリー
- GraphicsJS
(原文:Introducing GraphicsJS, a Powerful Lightweight Graphics Library)
[翻訳:薮田佳佑/編集:Livit]