HTML5 アプリケーションをビルドする

データの表示に HTML5 のキャンバスを使用する

Brandon Satrom

コード サンプルのダウンロード

オンライン化が始まり、Web が静的なテキストとリンクの集まりでしかなかったころから、これら以外のコンテンツをサポートすることへの関心は高まっていました。1993 年に Marc Andreessen (後に Netscape Navigator へと進化する Mosaic ブラウザーの生みの親) が、ページ上のテキストに画像をインラインで埋め込む標準として、IMG タグを提案しました。IMG タグはすぐさま Web ページにグラフィカル リソースを追加する事実上の標準となり、現在でもこの標準は現役です。さらに、Web の主体がドキュメントからアプリケーションに移り変わったことで、IMG タグはいっそう重要になったとも言えます。

一般に、メディアの重要性が以前よりも確実に高まっています。Web におけるメディアの需要はこの 18 年間で高まりましたが、画像は静的なままです。Web 製作者はサイトでオーディオ、ビデオ、インタラクティブ アニメーションなどの動的メディアをますます使用するようになっており、最近までは、Flash や Silverlight などのプラグインが主に使用されていました。

HTML5 が登場した現在、ブラウザーのメディア要素は大きな影響を受けています。新しい Audio タグと Video タグについて、おそらく耳にされたことがあるでしょう。この 2 つのタグを使用することでプラグインの必要がなくなり、このコンテンツがブラウザーの主役として機能するようになります。この両方の要素と、その API の詳細については来月取り上げる予定です。また、キャンバス要素についてもおそらく耳にされたことがあるでしょう。JavaScript の多種多様な API で操作する描画サーフェイスで、画像やアニメーションを実行時に作成および操作できます。IMG タグは静的なグラフィカル コンテンツに有効でしたが、キャンバス要素は動的かつスクリプトで操作するコンテンツに役立つ可能性があります。

キャンバス要素は魅力的ですが、その印象にちょっとした問題があります。キャンバスは処理能力が高く、そのデモには複雑なアニメーションやゲームがよく使用されます。このようなデモからキャンバスの能力がよくわかりますが、デモを見た人には、キャンバスの操作は複雑で難しく、アニメーションやゲームなどの複雑なコンテンツに使用するものだと考えられてしまっています。

今月は、キャンバスの派手で複雑そうな側面を避け、Web アプリケーションからデータを表示する優れた手段であることに的を絞って、単純かつ基本的な使用法について説明します。この点を念頭に、キャンバスを使い始める方法と、単純な直線、図形、およびテキストを描画する方法から説明を始めます。次に、図形のグラデーションを操作する方法と外部画像をキャンバスに追加する方法を取り上げます。最後に、この連載で毎回行っているように、ポリフィルによって以前のブラウザーでキャンバスをサポートする方法を簡単に紹介します。

HTML5 のキャンバスの概要

W3C が定める HTML5 の仕様 (w3.org/TR/html5/the-canvas-element.html、英語) によると、キャンバス要素は「スクリプトと、解像度に依存するビットマップ キャンバスを用意し、グラフ、ゲームのグラフィックなどのビジュアル画像を実行時にレンダリングできる」ようにします。キャンバスは、実際には W3C の 2 つの仕様で定義されています。最初の定義は、HTML5 の中核仕様で定められ、要素自体が詳しく規定されています。この仕様では、canvas 要素の使用方法、描画コンテキスト、キャンバスのコンテンツをエクスポートする API、およびブラウザー ベンダー向けのセキュリティの考慮事項を定義しています。2 つ目の定義は、HTML Canvas 2D Context 仕様 (w3.org/TR/2dcontext、英語) で定められています。これについては後ほど説明します。

キャンバスを使い始めるには、次のように <canvas> 要素を HTML5 マークアップに追加するだけです。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My Canvas Demo </title>               
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <canvas id="chart" width="600" height="450"></canvas>       
  </body>
</html>

これでキャンバス要素が DOM に追加されますが、キャンバス要素には明示的に追加するまでコンテンツがないため、このページのマークアップでは何も表示されません。ここで登場するのが描画コンテキストです。空のキャンバスの位置を示すため、CSS を使用してキャンバス要素にスタイルを指定し、空の要素の周囲に青い点線を追加します。

canvas {
    border-width: 5px;
    border-style: dashed;
    border-color: rgba(20, 126, 239, 0.50)
}

このページを Internet Explorer 9 以降、Chrome、Firefox、Opera、または Safari で開いた結果を図 1 に示します。

A Blank, Styled Canvas Element
図 1 スタイルを指定した空のキャンバス要素

キャンバスを使用すると、ほとんどの操作を JavaScript で処理し、キャンバスの描画コンテキストの公開済み API を使用してサーフェイスのピクセルを 1 つずつ操作できます。キャンバスの描画コンテキストを取得するには、DOM からキャンバス要素を取得して、この要素の getContext メソッドを呼び出す必要があります。

var _canvas = document.getElementById('chart');
var _ctx = _canvas.getContext("2d");

getContext メソッドは、当該キャンバスでの描画に使用できる API を備えたオブジェクトを返します。このメソッドの最初の引数 (ここでは "2d") は、キャンバスに使用する描画 API を指定します。"2d" は、先ほど触れた HTML Canvas 2D Context を指します。ご想像のとおり、2D とは、このコンテキストが 2 次元の描画コンテキストであることを意味します。この記事の執筆時点で幅広くサポートされている描画コンテキストは 2D コンテキストだけなので、今回はこのコンテキストを使用します。3D 描画コンテキストに関する開発と実験が続いていることから、今後はさらに優れたキャンバスが登場する見込みです。

直線、図形、およびテキストを描画する

ページにキャンバス要素を追加し、その描画コンテキストを JavaScript で取得したので、これでコンテンツを追加できるようになります。今回はデータの表示に的を絞るため、架空のスポーツ用品店の今月の売上データを表す棒グラフをキャンバスに描画します。この演習では、軸の直線、棒グラフの図形と塗りつぶし、および各軸と各棒グラフのラベル テキストを描画します。

まず、x 軸と y 軸の直線を描画します。キャンバスのコンテキストを使って直線 (パス) を描画するのは、2 段階の手順です。第 1 段階は、lineTo(x, y) と moveTo(x, y) の呼び出しを数回行い、サーフェイス上に直線を引きます。各メソッドは、canvas オブジェクト上の x 座標と y 座標 (原点は左上隅) を受け取って、操作の実行時に使用します (画面そのものの座標ではありません)。moveTo メソッドは指定した座標まで移動し、lineTo メソッドは現在の座標から指定した座標まで直線を引きます。たとえば、次のコードはサーフェイスに y 軸を引きます。

// Draw y axis.
_ctx.moveTo(110, 5);
_ctx.lineTo(110, 375);

このコードをスクリプトに追加してブラウザーで実行しても、何も起こりません。第 1 段階は線を引くだけなので、画面にはまだ何も描画されません。この段階では、ブラウザーに対し、将来のある時点で画面に描画するパス操作を記録するよう指示するだけです。パスを画面に描画する準備が整ったら、オプションでコンテキストの strokeStyle プロパティを設定し、stroke メソッドを呼び出します。このメソッドで、非表示の直線を塗りつぶします。結果を図 2 に示します。

 

// Define Style and stroke lines.
_ctx.strokeStyle = "#000";
_ctx.stroke();

A Single Line on the Canvas
図 2 キャンバス上の 1 本の直線

直線の定義 (lineTo と moveTo) と直線の描画 (stroke) が分かれているので、一連の lineTo 操作と moveTo 操作をバッチ処理してから、すべての直線を一度に画面に出力できます。この手法を使用して、x 軸と y 軸の両方を描画し、各軸の端に矢印を描画します。軸を描画する関数全体を図 3 に、その結果を図 4 に示します。

図 3 drawAxes 関数

function drawAxes(baseX, baseY, chartWidth) {
   var leftY, rightX;
   leftY = 5;
   rightX = baseX + chartWidth;
   // Draw y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX, baseY);
   // Draw arrow for y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX + 5, leftY + 5);
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX - 5, leftY + 5);
   // Draw x axis.
   _ctx.moveTo(baseX, baseY);
   _ctx.lineTo(rightX, baseY);
   // Draw arrow for x axis.
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY + 5);
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY - 5);
   // Define style and stroke lines.
   _ctx.strokeStyle = "#000";
   _ctx.stroke();
}

Completed X- and Y-Axes
図 4 完成した X 軸と Y 軸

これで軸が表示されましたが、軸にラベルを付ければもっと便利になります。2D キャンバス コンテキストでは、テキストをキャンバス要素に追加する API が仕様にあるため、キャンバス要素に重ねてテキストを表示するような煩雑な工夫を凝らす必要はありません。とは言え、キャンバスのテキストにはボックス モデルが存在せず、ページ全体のテキストに対して定義された CSS スタイルなども適用されません。確かに、API には CSS のフォント規則と同様に機能する font 属性に加えて、指定した座標に対する相対位置をある程度制御できる textAlign プロパティや textBaseline プロパティも用意されています。しかし、これらの機能を除けば、キャンバスにテキストを描画するには、指定したテキストのキャンバスにおける正確な位置を指定する必要があります。

x 軸は架空のスポーツ用品店の商品 (product) を表すため、x 軸に次のようにラベルを付けます。

var height, widthOffset;
height = _ctx.canvas.height;
widthOffset = _ctx.canvas.width/2;
_ctx.font = "bold 18px sans-serif";
_ctx.fillText("Product", widthOffset, height - 20);

このコードでは、オプションの font プロパティを設定し、サーフェイスに描画する文字列を指定して、文字列の開始位置として使用する x 座標と y 座標を指定しています。この例では、キャンバスの中央、下端から 20 ピクセル上の位置に "Product" という単語を描画します。このように軸線とテキストの間を空けて、棒グラフの各商品に付けるラベルの領域を確保します。各商品の売上データを表示する y 軸のラベルについても、同様の処理を施します。結果を図 5 に示します。

Canvas with Text
図 5 テキストを添えたキャンバス

これで、棒グラフの枠組みが完成しました。次は、棒グラフに使用するダミーの売上データを作成しましょう。これは、オブジェクト リテラルの JavaScript 配列として定義します。

var salesData = [{
   category: "Basketballs",
   sales: 150
}, {
   category: "Baseballs",
   sales: 125
}, {
   category: "Footballs",
   sales: 300
}];

このデータがあれば、fillRect メソッドと fillStyle プロパティを使用して、グラフにそれぞれの棒グラフを描画できます。

fillRect(x, y, width, height) メソッドは、指定した幅と高さで、キャンバスの x 座標と y 座標に四角形を描画します。ここで重要なのは、fillRect メソッドは、左上隅から外側に向かって広がる図形を描画しますが、幅と高さに負の値を指定した場合は、塗りつぶしが逆方向に行われる点です。つまり、グラフ作成などの描画処理の場合は、棒グラフを下から上へではなく上から下へ描画します。

棒グラフを描画するには、売上データの配列をループ処理し、適切な座標を指定して fillRect メソッドを呼び出します。

var i, length, category, sales;
var barWidth = 80;
var xPos = baseX + 30;
var baseY = 375;       
for (i = 0, length = salesData.length; i < length; i++) {
   category = salesData[i].category;
   sales = salesData[i].sales;
   _ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);
   xPos += 125;
}

このコードでは、各棒グラフの幅は一定ですが、高さは配列に含まれる各商品の sales プロパティから計算しています。このコードの結果を図 6 に示します。

Rectangles as Bar Chart Data
図 6 棒グラフのデータを表す四角形

これで、数値の上では正確なグラフができましたが、黒一色の棒グラフには改善の余地があります。グラフに色を付けて見栄えを良くし、グラデーション効果を追加しましょう。

色とグラデーションを操作する

描画コンテキストの fillRect メソッドを呼び出すと、コンテキストによって、描画する四角形のスタイルが、現在の fillStyle プロパティを使用して指定されます。既定のスタイルは単色の黒で、グラフが図 6 のように表示されるのはこのためです。fillStyle プロパティは、名前付きの色、16 進数による色、および RGB 色を受け取るので、棒グラフの描画前にスタイルを指定する機能を追加しましょう。

// Colors can be named hex or RGB.
colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];       
...
_ctx.fillStyle = colors[i % length];
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

まず、色の配列を作成します。次に、各商品をループ処理しながら、いずれかの色をその要素の塗りつぶしスタイルに使用します。結果を図 7 に示します。

Using fillStyle to Style Shapes
図 7 fillStyle プロパティを使用した図形のスタイル指定

これで見栄えが良くなりましたが、fillStyle プロパティは柔軟性が非常に高いので、単色だけでなく線形グラデーションや放射状グラデーションも使用できます。2D 描画コンテキストの仕様には、createLinearGradient と createRadialGradient という 2 つのグラデーション関数があります。どちらの関数も、滑らかに色を遷移することで、図形のスタイルを強化します。

この例では、次のような createGradient 関数を定義します。この関数は、グラデーションの x 座標と y 座標、幅、および使用する主要色を受け取ります。

function createGradient(x, y, width, color) {
   var gradient;
   gradient = _ctx.createLinearGradient(x, y, x+width, y);
   gradient.addColorStop(0, color);
   gradient.addColorStop(1, "#efe3e3");
   return gradient;
}

開始座標と終了座標を指定して createLinearGradient 関数を呼び出したら、描画コンテキストから返された gradient オブジェクトに 2 つの色境界を追加します。addColorStop メソッドは、グラデーションの方向に沿って色の遷移を追加します。このメソッドは、最初のパラメーターの値が 0 ~ 1 であれば、任意の回数呼び出せます。gradient オブジェクトの設定が完了したら、関数が gradient オブジェクトを返します。

gradient オブジェクトは、前の例で指定した 16 進数による色や RPB 色の代わりに、コンテキストの fillStyle プロパティに設定できます。ここでは、開始位置に前の例と同じ色を指定し、これらの色を薄い灰色に遷移します。

colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];
_ctx.fillStyle = createGradient(xPos, baseY - sales-1, barWidth, colors[i % length]);
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

グラデーション オプションの結果を図 8 に示します。

Using Gradients in a Canvas
図 8 キャンバスでのグラデーションの使用

画像を操作する

この時点で、グラフは非常に見栄えが良くなっており、数十行の JavaScript を使用してブラウザーでレンダリングできます。ここで作業を完了してもよいのですが、画像の操作に関するキャンバスの基本 API をもう 1 つ説明しておきましょう。キャンバスでは、スクリプト ベースで対話型のコンテンツを静的画像の代わりに使用できるだけでなく、静的画像を使用してキャンバスの見た目を強化することもできます。

このデモでは、棒グラフの棒に画像を使用します。しかも、単なる画像ではなく、商品自体の写真を使用します。この目標を踏まえて、デモの Web サイトには各商品の JPG 画像 (この例では、basketballs.jpg、baseballs.jpg、および footballs.jpg) が含まれたフォルダーを用意しています。必要な作業は、各画像を適切に配置してサイズ指定するだけです。

2D 描画コンテキストでは drawImage メソッドが定義され、それぞれ 3 つ、5 つ、または 9 つのパラメーターを受け取る 3 つのオーバーロードがあります。どのオーバーロードも、最初のパラメーターは描画する DOM 要素です。drawImage メソッドの最も単純なオーバーロードは、キャンバスの x 座標と y 座標を受け取り、その座標位置に画像を描画します。また、4 つ目と 5 つ目のパラメーターに幅と高さを指定することも可能です。これらのパラメーターを指定すると、サーフェイスに描画する前に、指定したサイズに画像が拡大縮小されます。最後に、drawImage の最も複雑な使用法では、画像を指定した四角形にトリミングし、トリミング済みの画像を指定したサイズに拡大縮小して、キャンバス上の指定した座標に描画します。

用意しているソースの画像は、サイトの他の場所で使用している大きなサイズの画像なので、ここでは最後の手法を使用することにします。この例では、salesData 配列のループ処理で商品ごとに fillRect メソッドを呼び出すのではなく、Image DOM 要素を作成し、そのソースに商品画像の 1 つを設定して、この画像をトリミングしたものをグラフにレンダリングします (図 9 参照)。

図 9 キャンバスでの画像の描画

// Set outside of my loop.
xPos = 110 + 30;     
// Create an image DOM element.
img = new Image();
img.onload = (function(height, base, currentImage, currentCategory) {
  return function() {
    var yPos, barWidth, xPos;
    barWidth = 80;
      yPos = base - height - 1;
    _ctx.drawImage(currentImage, 30, 30, barWidth, height, xPos, yPos,
      barWidth, height);
      xPos += 125;           
  }
})(salesData[i].sales, baseY, img, salesData[i].category);
img.src = "images/" + salesData[i].category + ".jpg";

これらの画像は、設計時に手動でマークアップに追加するのではなく動的に作成しているため、画像のソースを設定すればすぐにその画像をキャンバスに描画できると考えるのは禁物です。各画像を完全に読み込んだ場合にのみ描画するために、画像の onload イベントに描画ロジックを追加し、このコードを自己呼び出し関数でラップすることで、正しい商品カテゴリ、売上、および位置の変数を参照する変数を使用するクロージャを作成します。図 10 に結果を示します。

Using Images on a Canvas
図 10 キャンバスでの画像の使用

キャンバスのポリフィルを使用する

ご存じのように、Internet Explorer 9 より前のバージョンの Internet Explorer や、他のブラウザーの以前のバージョンでは、キャンバス要素がサポートされていません。この問題を確認するには、デモ プロジェクトを Internet Explorer で開き、F12 キーを押して開発者ツールを開きます。このツールで、ブラウザー モードを Internet Explorer 8 または Internet Explorer 7 に変更し、ページを更新します。ページを更新すると、おそらく JavaScript 例外が発生し、「オブジェクトは getContext メソッドまたはプロパティをサポートしていません。」というメッセージが表示されます。2D 描画コンテキストも、キャンバス要素自体も使用できません。また、Internet Explorer 9 であっても、DOCTYPE を指定しない限りキャンバスを使用できない点を把握しておくことも重要です。この連載の最初の記事 (msdn.microsoft.com/magazine/hh335062) で述べたように、すべての HTML ページの先頭で必ず <!DOCTYPE html> を使用して、ブラウザーの最新機能を使用できるようにすることをお勧めします。

キャンバスがサポートされていないブラウザーのユーザーに対して実施できる最も簡単な対策は、画像やテキストなどのフォールバック要素を使用することです。たとえば、フォールバック画像をユーザーに表示するには、次のようなマークアップを使用します。

<canvas id=”chart”>
  <img id=”chartIMG” src=”images/fallback.png”/>
</canvas>

<canvas> タグの内部に配置したすべてのコンテンツは、ユーザーのブラウザーでキャンバスがサポートされない場合にのみレンダリングされます。したがって、単純な、確認なしのフォールバックとして、キャンバスの内部に画像やテキストを配置できます。

より高度なフォールバック サポートを実施する必要がある場合、さいわいなことにキャンバスにはさまざまなポリフィル ソリューションが用意されているので、使用できるソリューションを注意深く調査し、特定のポリフィルの制限に留意すれば、以前のブラウザーでも快適にキャンバスを使用できます。この連載の他の記事で説明したように、どの HTML5 テクノロジのポリフィルを探す場合も、まず GitHub の Modernizr wiki で公開されている「HTML5 の複数のブラウザーのポリフィル」(bit.ly/nZW85d、英語) ページを参照してください。この記事の執筆時点で は、Flash と Silverlight にフォールバックする 2 つのポリフィルなど、複数のキャンバス向けポリフィルが公開されています。

この記事のダウンロード可能なデモ プロジェクトでは、Internet Explorer でサポートされている Vector Markup Language (VML) を使用してキャンバス機能にそっくりな類似機能を作成する explorercanvas (code.google.com/p/explorercanvas、英語) と、以前のブラウザーでテキストをレンダリングするためのサポートを追加する canvas-text (code.google.com/p/canvas-text、英語) を使用しています。

以前の連載で説明したように、Modernizr を使用して、ブラウザーにおけるキャンバス (および canvastext) の機能検出をサポートできます。そのためには、Modernizr.canvas を呼び出してから、Modernizr.load を使用して必要に応じて非同期に explorercanvas を呼び出します。詳細については、modernizr.com (英語) を参照してください。

Modenrizr を使用しない場合は、条件付きコメントという方法でも、条件に応じて以前のバージョンの IE 向けに explorercanvas を追加できます。

<!--[if lt IE 9]>
  <script src="js/excanvas.js"></script>
  <script src="js/canvas.text.js"></script>
<![endif]-->

Internet Explorer 8 以前のバージョンでは、このような形式のコメントが検出されると、コメントのブロックが if ステートメントとして実行され、explorercanvas と canvas-text のスクリプト ファイルが読み込まれます。Internet Explorer 10 など他のブラウザーでは、ブロック全体がコメントとして処理され、ブロック内の処理は無視されます。

開発中のアプリケーションに使用するポリフィルを評価する場合は、そのポリフィルでサポートされる 2D 描画コンテキストの数を必ず調べてください。すべての使用法を完全にサポートするポリフィルはほとんどありませんが、この記事で紹介した基本的な使用法は、ほぼすべてのポリフィルで処理できます。

今回すべてを取り上げることができませんでしたが、キャンバスを使用してできることは他にもたくさんあります。たとえば、クリック イベント (およびその他のイベント) への応答、キャンバスのデータの変更などの簡単な処理から、描画サーフェイスのアニメーション化、ピクセル単位の画像のレンダリングと操作、状態の保存、独自の画像としてのサーフェイス全体のエクスポートなどの高度な処理を実行できます。実際、キャンバスについては何冊もの書籍が刊行されています。ゲーム開発者でなくてもキャンバスの威力を体験できるのです。この記事での基本事項の説明を通じて、この点について納得していただけたことを願っています。ぜひご自分で仕様をお読みになり、この魅力的な新しいグラフィックス テクノロジに正面から取り組んでみてください。

Internet Explorer 9 におけるキャンバスのサポートの詳細については、オンラインの Internet Explorer 9 開発者ガイド (msdn.microsoft.com/ie/ff468705) を参照してください。また、IE 試用版サイト (bit.ly/9v2zv5、英語) で公開されている Canvas Pad デモもご覧ください。キャンバスに関連するその他いくつかのクロスブラウザー ポリフィルについては、bit.ly/eBMoLW (英語) のポリフィル一覧を参照してください。

今回紹介したすべてのデモ (オンラインで閲覧可能) は、WebMatrix という、マイクロソフトが無償で提供している軽量の Web 開発ツールを使用してビルドしています。WebMatrix は、aka.ms/webm で試すことができます。

Brandon Satrom は、テキサス州オースティン郊外でマイクロソフトの開発者エバンジェリストとして活躍しています。彼のブログは userinexperience.com (英語) で、Twitter は twitter.com/BrandonSatrom (英語) でフォローすることができます。

この記事のレビューに協力してくれた技術スタッフの Jatinder MannClark Sell に心より感謝いたします。