HTML5 で新たに導入された canvas 要素は、図の描画を JavaScript だけで実現することができます。これまでウェブ・ページに動的に図を描くには、Adobe Flash や Microsoft SilverLight などのプラグインを使う必要がありました。
canvas 要素は、動的に図を描く必要があるコンテンツの他にも、アニメーション、ゲームのグラフィック、データの可視化、写真加工、リアルタイム動画処理などに使用することができます。もし、いつ誰が見ても同じイメージであれば、canvas 要素を使う必要はないでしょう。そのようなイメージは img 要素のほうが適しています。
また、デコレーション用のイメージであれば、スタイルシートを使うべきです。canvas 要素の乱用は避けるべきです。
canvas 要素は全体的に 2D グラフィックを対象としています。 WebGL API は、こちらも canvas 要素を使用して、ハードウェアで高速化された 2D および 3D グラフィックを描きます。
canvas 要素の基本的な描画サンプル
canvas 要素には、width 属性と height 属性が規定されており、それぞれ、canvas 要素で描かれる領域の横幅と縦幅を CSS ピクセルで指定します。
canvas 要素のマークアップ例
<canvas width="300" height="150"></canvas>
これだけでは描画領域を設定しただけなのでまだ何も表示されておりませんが、もし指定がなければ、横幅 300ピクセル、縦幅 150ピクセルとして処理されます。
canvas 要素の中には、JavaScript で描かれる図のフォールバック・コンテンツとして用意したコンテンツを入れます。canvas 要素に対応していないブラウザやスクリーン・リーダーなどでは、このフォールバック・コンテンツが利用されることが期待されています。
canvas 要素のコンテンツ・モデルはトランスペアレントです。そのため、canvas 要素の中に入れることができるコンテンツは、該当の canvas 要素の親要素のコンテンツ・モデルに依存します。
canvas 要素の中に入れるコンテンツは、フォールバック・コンテンツとして役に立つようなものを入れるべきです。つまり、アクセシビリティを考慮することが期待されます。
canvas 要素のマークアップ例
<canvas width="300" height="150">
<!-- ここにフォールバック・コンテンツ入れます。 -->
</canvas>
このように、canvas 要素の開始タグと終了タグの中に、フォールバック・コンテンツを入れます。こうすることで、もしブラウザが canvas 要素に対応していなかったり、JavaScript を無効にしていれば、フォールバック・コンテンツが表示されることになります。
では、フォールバック・コンテンツとして何を用意すれば良いでしょうか。フォールバック・コンテンツは、「 canvas 要素を何のために使っているのか」「どこまでフォールバックさせたいのか」「 canvas 要素以外の手段で同じものが実現できるか」「制作にどれだけコストをかけることができるのか」、また「その canvas 要素が表すコンテンツがどれほど重要なのか」などによって、大きく異なってきます。そのため、一概に、こうあるべきだと言い切ることはできませんが、やはり、もし canvas 要素が利用できたなら、どうなっていたかをイメージできる内容をフォールバック・コンテンツとして用意するのが良いでしょう。
例えば、canvas 要素にグラフを描画したいなら、そのフォールバック・コンテンツは、そのグラフの元となるデータの表(table 要素)が適切でしょう。
このように、canvas 要素に表される図画の代替となる情報を、出来る限りフォールバック・コンテンツとして用意してあげるのが重要です。
canvas 要素のフォールバック例
<canvas width="300" height="150">
図形を表示するには、canvas 要素をサポートしたブラウザが必要です。
</canvas>
canvas 要素はマークアップしただけでは何の役にも立ちません。JavaScript を使って図を描くことで初めて意味のあるものになります。スクリプトは別途説明予定ですが、ここでは、canvas 要素の描画方法の基礎を簡単に紹介します。
ここでは、ちょっとしたデコレーションを伴った時計を作ってみましょう。このサンプルを通して、線の描画、円の描画、色の指定、テキストの描画、円形グラデーション、アニメーションなどの一通りの基礎を学ぶことができます。
このサンプルでは、左上に日付と時間が表示されますが、1秒毎に更新されます。そして、6時から 18時までは太陽が表示されます。そして 18時から 6時までは月が表示されます。太陽と月は、時間に合わせて位置が移動していきます。
夜間は、月だけでなく、星がキラキラ光るようなデコレーションを施しており、1秒毎に、表示される星の位置がランダムに変化します。
日中の表示例
夜間の表示例
では、まずHTMLをご覧頂きましょう。
HTML
<canvas id="canvas" width="300" height="150"></canvas>
この canvas 要素には、JavaScript から要素を特定しやすくするために id コンテンツ属性を指定しています。また、canvas 要素のサイズを指定するために、width コンテンツ属性と height コンテンツ属性を指定しています。
では、次に、サンプルのスクリプトを見ていきます。スクリプトの全文はサンプルページに載せています。このスクリプトは、head 要素の中の script 要素の中に直接入れても構いませんし、別ファイルとして用意して、それを script 要素の src コンテンツ属性でロードしても構いません。どちらでも動作するように作られています。
このスクリプトで使われている IDL 属性やメソッドを詳しく見ていきましょう。
canvas で図形を描くためには、まず、canvas を参照したオブジェクトから、canvas コンテキストを取得しなければいけません。canvas コンテキストとは、canvas で図形を描くために必要なメソッドやプロパティがセットされたオブジェクトです。canvas コンテキストのオブジェクトは、canvas を参照したオブジェクトに組み込まれた getContext('2d')
メソッドを使って取得します。
canvas コンテキストの取得
window.addEventListener("load", function() {
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
setInterval(draw, 1000);
}, false);
getContext() メソッドには1つだけ引数を与えますが、それは '2d'
で固定です。将来的に '3d'
もサポートされる可能性はありますが、現時点では、'2d'
のみがサポートされています。
これ以降は、getContext() メソッドから取り出された canvas コンテキストの context オブジェクトを通して、canvas に図形を描いていきます。
このコードでは、最後に、setInterval() メソッドを使って、1秒おきに draw 関数を呼び出すようにしています。draw 関数では、現在の日時に合わせて canvas に図形を描きます。この関数が 1秒毎に呼び出されることで、アニメーションが実現できるのです。
アニメーションを実現するためには、canvas に 1コマ描く前に、canvas にすでに描かれているコマをクリアしなければいけません。クリアするためには、clearRect() メソッドを使います。クリアした後に、次のコマを描きます。
コマの描画の前処理
function draw() {
// canvasをクリア
context.clearRect(0, 0, 300, 150);
// 現在の日時を取得
var t = new Date();
// 背景を描画
drawBackGround(t);
// 太陽または月を描画
drawSunMoon(t);
// 日時を描画
drawDatetime(t);
}
clearRect() メソッドは、引数で指定された短形の領域を完全にクリアします。このメソッドには、短形の左上端の x 座標、短形の左上端の y 座標、短形の横幅、短形の縦幅の順に、4つの引数を与えます。
canvas の座標は、左上端を原点 (0,0) とし、右に向かって x 座標が大きくなり、下に向かって y 座標が大きくなります。
このサンプルでは、昼間と夜間とで背景の色を変えます。そのためには、まず、canvas の領域全体を塗りつぶす必要があります。短形の色の指定には、fillStyle 属性を使います。そして、短形の塗りつぶし処理には fillRect() メソッドを使います。
背景の描画処理
function drawBackGround(t) {
// 現在のcanvasの状態を保存
context.save();
// 背景の色を時間によって確定
var h = t.getHours();
if( h >= 6 && h < 18 ) {
context.fillStyle = "#a4eaf6";
} else {
context.fillStyle = "#000044";
}
// 背景を塗りつぶす
context.fillRect(0, 0, 300, 150);
// canvasの状態を元に戻す
context.restore();
}
色は描画の前に指定しなければいけません。つまり、fillStyle属性に色をセットしてから、fillRect() メソッドを呼び出す必要があります。
fillStyle 属性には、CSS で使われる色の指定方法であれば、どのフォーマットを使って指定しても構いません。ここでは、16進数表記で色を指定しています。
fillRect() メソッドには、短形の左上端の x 座標、短形の左上端の y 座標、短形の横幅、短形の縦幅の順に、4つの引数を与えます。これは、clearRect() メソッドと同じです。
なお、この関数の最初では save() メソッドが呼び出され、そして最後で restore() メソッドが呼び出されています。これは、canvas では、fillStyle 属性などでセットされた色などの情報は、この関数の処理が終わっても、そのまま引き継がれてしまうからです。このため、複雑な描画を行っていると、現在の状態が分かりにくくなります。そこで、save() メソッドを呼び出すことで、この関数の処理が実行される前の状態を保存します。そして、この関数の処理が終わった時点で、restore() メソッドを呼び出すことで、先ほど保存した状態を復帰させているのです。
このサンプルでは、canvas の状態を変更する処理が含まれる関数すべてで save() メソッドと restore() メソッドを呼び出しています。canvas のスクリプティングのテクニックの 1つとして覚えておくと便利です。
太陽の描画においては、円の描画と円形グラデーションを使っています。
太陽の描画処理
function drawSun(t) {
// 現在のcanvasの状態を保存
context.save();
// 太陽を描画
var rad = getAngle(t);
var p = getArcArg(rad);
context.beginPath();
context.arc(p.x, p.y, p.radius, p.startAngle, p.endAngle, p.anticlockwise);
var grad = context.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.radius);
grad.addColorStop(0, "#ffffff");
grad.addColorStop(0.5, "#FFFB13");
grad.addColorStop(1, "#FFCB1D");
context.fillStyle = grad;
context.fill();
// canvasの状態を元に戻す
context.restore(t);
}
canvas では、図形を描く場合、まず最初に beginPath() メソッドを呼び出します。これは、これからパスを描き始めることを宣言するものです。パスとは、図形の輪郭を表す直線や曲線のことを表し、さまざまなメソッドを繰り返し使うことで、一筆書きをするようにパスをつなげて、図形を定義します。
先ほど使った clearRect() メソッドや fillRect() メソッドでは、事前に beginPath() メソッドを呼び出す必要はありませんが、これは canvas の中では例外と考えてください。
beginPath() メソッド
context.beginPath();
canvas では、beginPath() メソッドが呼び出されない限り、保持されているパスがクリアされません。そのため、いくつもの図形を描きたい場合には、その都度、事前に beginPath() メソッドを呼び出してください。
次に円のパスを描きます。円を描くためには、arc() メソッドを使います。このメソッドには、円の中心の x 座標、円の中心の y 座標、円の半径、円を描き始める位置の角度、円を描き終える位置の角度、円を描く方向の順番で、6つの引数を与えます。
arc() メソッド
context.arc(p.x, p.y, p.radius, p.startAngle, p.endAngle, p.anticlockwise);
4つ目と 5つ目の引数が表す角度の単位はラジアンです。また、6つ目の引数が表す方向には true か false を指定します。true を指定すると反時計回りに、false を指定すると時計回りに円を描画することになります。
この例では、arc() メソッドの引数を getArcArg() 関数で計算させています。
getArcArg() 関数
function getArcArg(rad) {
var arg = {
x: 150 + Math.cos(rad) * 100,
y: 150 - Math.sin(rad) * 100,
radius: 30,
startAngle: 0,
endAngle: Math.PI * 2,
anticlockwise:false
};
return arg;
}
arc() メソッドを呼び出しただけでは、まだパスを定義しただけに過ぎず、canvas に描かれることはありません。ここでは、円を描画する前に、円に対して円形グラデーションを定義します。
円形グラデーションを実現するためには、まず createRadialGradient() メソッドを使って、グラデーション・オブジェクトを取り出します。この例では、そのオブジェクトを変数 grad にセットしています。
このメソッドには、グラデーションを開始する円を表す 3つの引数と、グラデーションを終了する円を表す 3つの引数の、合計 6つの引数を与えます。それぞれの 3つの引数は、円の中心の x 座標、円の中心の y 座標、円の半径の順番で与えます。
createRadialGradient() メソッド
var grad = context.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.radius);
この例では、太陽の中心の点を表す円(中心が太陽と同じで半径が 0 の円)から、太陽の輪郭を表す円(中心と半径が太陽と同じ円)に向かう円形グラデーションを定義しています。
この状態では、まだ具体的な色が定義されていません。円形グラデーションの色を定義するには、オブジェクト grad に定義されている addColorStop() メソッドを使います。このメソッドには、グラデーションの位置を表す 0 から 1 までの範囲の数値、CSS で使われる色を表す文字列の順に、2つの引数を与えます。
addColorStop() メソッド
grad.addColorStop(0, "#ffffff");
grad.addColorStop(0.5, "#FFFB13");
grad.addColorStop(1, "#FFCB1D");
この例では、太陽の中心位置を白に、太陽の半分の半径の位置を黄色に、太陽の外縁をオレンジに定義しています。ここでは、グラデーションのポイントを 3つ指定していますが、いくつでも指定可能です。ただし、最低 2つ指定しないとグラデーションになりませんので注意してください。
円形グラデーションの色を定義したら、fillStyle IDL 属性に、変数 grad をセットします。
fillStyle IDL 属性
context.fillStyle = grad;
これで、円のパス、そして円に描く円形グラデーションが定義されました。
ただし、まだ、この時点では canvas に太陽は描かれません。最後に fill() メソッドを呼び出すことで、初めて canvas に太陽が現れます。
fill() メソッド
context.fill();
月の描画は、太陽と比べて簡単です。ただ単に黄色の円を描きます。そして、背景と同じ色の円を、少し位置をずらして描きます。こうすることで、月が欠けたように見えます。
月の描画処理
function drawMoon(t) {
// 現在の canvas の状態を保存
context.save();
// 月を描画
var rad = getAngle(t);
var p = getArcArg(rad);
context.fillStyle = "yellow";
context.beginPath();
context.arc(p.x, p.y, p.radius, p.startAngle, p.endAngle, p.anticlockwise);
context.fill();
// 月の満ち欠けを描画
var q = getArcArg(rad - Math.PI / 24);
context.fillStyle = "#000044";
context.beginPath();
context.arc(q.x, q.y, q.radius, q.startAngle, q.endAngle, q.anticlockwise);
context.fill();
// canvas の状態を元に戻す
context.restore();
}
円を描くために、arc() メソッドを使っていますが、これは太陽の描画と同じです。また、月はグラデーションを伴わないため、fillStyle IDL 属性に CSS カラーを表す文字列をセットしています。
1つの星は、ただ単に、2本の黄色の線を十字に描いただけです。そして、20個の星をランダムな位置に描画しています。
星の描画処理
function drawStars() {
// 現在のcanvasの状態を保存
context.save();
// 星をランダムに描画
context.strokeStyle = "rgba(255, 255, 0, 0.5)";
for( var i=0; i<20; i++ ) {
var s = getRandomPos();
context.beginPath();
context.moveTo(s.x-2, s.y);
context.lineTo(s.x+2, s.y);
context.stroke();
context.beginPath();
context.moveTo(s.x, s.y-4);
context.lineTo(s.x, s.y+4);
context.stroke();
}
// canvasの状態を元に戻す
context.restore();
}
まず最初に strokeStyle IDL 属性を使って色をセットしています。先ほど fillStyle IDL 属性を使って円の色やグラデーションをセットしましたが、fillStyle IDL 属性は塗りつぶしを定義するものです。それに対して、ここでは線、つまりパスに対して色を指定するため、strokeStyle IDL 属性を使います。
strokeStyle IDL 属性
context.strokeStyle = "rgba(255, 255, 0, 0.5)";
strokeStyle IDL 属性には、CSS で使う色を表す文字列であり、ブラウザが対応していれば、どんなフォーマットでも指定することが出来ます。ここでは、rgba フォーマットを使って、黄色の半透明を指定しています。次に 20回のループを使って星を描きますが、それぞれの星の描き方を見ていきましょう。
まず線を引くためには、最初にパスを描き始めることを宣言するために beginPath() メソッドを呼び出します。次に、開始点を定義するために moveTo() メソッドを呼び出します。
moveTo() メソッド
context.moveTo(s.x-2, s.y);
moveTo() メソッドには、開始点を表す x 座標と y 座標の、2つの引数を与えます。
次に、lineTo() メソッドを使って、直線を定義します。
lineTo() メソッド
context.lineTo(s.x+2, s.y);
lineTo() メソッドには、直線の終着点を表す x 座標と y 座標の、2つの引数を与えます。直線を引くために、2つの地点の座標を与えるわけではないところに注意してください。canvas でパスを描く際には、どのメソッドでも、手前の終着点を開始点と見なして処理されます。そのため、lineTo() メソッドには終着点だけを指定すれば良いのです。この例では、moveTo() メソッドで指定した座標を開始点として、lineTo() メソッドで指定した座標を終着点とする直線を表すパスが定義されたことになります。
この時点では、まだパスが定義されただけですので、canvas に直線は描かれていません。 canvas に線を描くためには、stroke() メソッドを呼び出します。
stroke() メソッド
context.stroke();
stroke() メソッドには引数はありません。このメソッドが呼び出された時点で定義されているパスが表す輪郭を一気に描画します。
太陽を描いた時には、fill() メソッドを使いましたが、fill() メソッドは塗りつぶし用のメソッドです。つまりパスの内側を塗りつぶします。それに対して stroke() メソッドはパスが表す線を描画するためのメソッドです。これらの違いを、しっかりと理解してください。
以上で横向きの一本の線が描かれました。縦向きの線を描くために、これまでと同じ処理をもう一度行います。これで十字が完成です。
canvas では図形だけではなく、文字を描画することもできます。このサンプルでは日付と時間を文字として描いています。
テキストの描画処理
function drawDatetime(t) {
// 現在のcanvasの状態を保存
context.save();
// 日付の文字列
var yer = (t.getYear() + 1900).toString();
var mon = ((t.getMonth() + 1).toString()).replace(/^(\d)$/, "0$1");
var dat = ((t.getDate()).toString()).replace(/^(\d)$/, "0$1");
var wek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sut"][t.getDay()];
var date = yer + "-" + mon + "-" + dat + "(" + wek + ")";
// 時間の文字列
var hur = ((t.getHours()).toString()).replace(/^(\d)$/, "0$1");
var min = ((t.getMinutes()).toString()).replace(/^(\d)$/, "0$1");
var sec = ((t.getSeconds()).toString()).replace(/^(\d)$/, "0$1");
var time = hur + ":" + min + ":" + sec;
// canvasに描画
var h = t.getHours();
if( h >= 6 && h < 18 ) {
context.fillStyle = "#000044";
} else {
context.fillStyle = "#e3f4fe";
}
context.font = "18px 'Monotype Corsiva'";
context.fillText(date, 10, 30);
context.fillText(time, 10, 50);
// canvasの状態を元に戻す
context.restore();
}
文字を描く前に、まず fillStyle IDL 属性を使って文字の色を指定しておきます。次に、font IDL 属性を使ってフォントを定義します。
font IDL 属性
context.font = "18px 'Monotype Corsiva'";
font IDL 属性には、CSS の font プロパティに指定できる文字列を指定します。
文字の色とフォントを指定したら、fillText() メソッドを呼び出して、canvas に文字を描画します。
fillText() メソッド
context.fillText(date, 10, 30);
fillText() メソッドには、描画したい文字列、x 座標、y 座標の順に、3つの引数を与えます。指定した座標は、デフォルトでは、描画する文字列の左下端を表すことになります。左上端ではないので、注意してください。
以上、簡単に canvas の使い方を解説してきましたが、HTML5 仕様では、もっと多くのメソッドや属性が規定されています。例えば、img 要素のイメージの組み込み、video 要素のフレームの組み込み、ベジェ曲線の描画、テキスト・シャドー描画などです。また、canvas に描かれたピクセル情報を配列データとして取り出して、そのイメージを加工することすら可能となります。
こうした canvas の詳細については、HTML5 仕様の原文か、または、HTML5.JP の Canvas チュートリアルやリファレンスをご覧ください。