基本手順
- HTMLでのcanvas要素の設置、またはスクリプトでcanvas要素の動的な作成と埋め込み
- canvasオブジェクトの取得と描画コンテキストの取得
- 描画コンテキストに対する描画
canvas要素の準備
HTMLに書く場合
id、幅と高さのほか、背景色や枠線など一般的なスタイルも設定可能。idを通して、スクリプトでcanvas要素のオブジェクトを取得する。
1 2 3 4 5 |
<canvas id="canvas1" width="300" height="200"></canvas> <canvas id="canvas2" width="300" height="300" style="background-color: blue"></canvas> <canvas id="canvas3" width="400" height="300" style="border:1px solid #000"></canvas> |
canvasオブジェクトと描画コンテキストの取得
基本形
HTML要素でcanvasを配置し、JavaScriptで取得して操作する手順。
canvasオブジェクトに描画するために描画コンテキストを取得するが、その取得が可能かどうかの判定が必要。コンテキストは今のところ2Dのみ提供されている。
JavaScriptによる標準的な手順
※”2d”の引数を”2D”とすると読み込んでくれないので要注意。また、後述の注意点を参照。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<canvas id="canvas_test" width="400" height="300" style="border:1px solid #000"></canvas> <script> canvasTest(); function canvasTest() { var canvas = document.getElementById( "canvas_test" ); if ( canvas.getContext ) { var ctx = canvas.getContext( "2d" ); ctx.fillRect( 50, 50, 300, 200 ); } } </script> |
実行結果
CoffeeScriptとjQueryによる標準的な手順
1 2 3 4 5 |
jQuery ($) -> canvas = $("#canvas_test")[0] if canvas.getContext context = canvas.getContext("2d") context.fillRect(0, 0, 400, 300) |
注意点~オブジェクト取得のタイミング
先の基本手順のスクリプトファイルをheadセクションで読み込んで実行すると、canvasオブジェクトが適正に読み込まれず、コンテキストがnullになってしまう。これは、canvasオブジェクトが形成される前にheadセクションでcanvasオブジェクトの処理を行うことになるため。
解決策として、DOMがすべて読み込まれてから必要なオブジェクトを取得するようにする。
スクリプトの読み込み位置を最後にする
外部ファイルをscript要素で読み込む場所を、</body>
タグの直前のように最後に持ってくる。
DOMの読み込みが終わってから取得する
たとえばjQueryのjQuery(document).ready()を実装して、DOMがすべて読み込まれてから実行することを保証する。
基本の描画方法
単一の矩形
矩形や円弧など、単一の図形を一つの命令で書く方法。矩形の外形を描くstrokeRect()
などが準備されている。
パスを指定する方法
以下の手順をとる。
- 必要に応じて、線の色や太さ、塗りつぶしの色などのスタイルを設定~
strokeStyle
など - 前のパスをリセットし、新しいパスの描画を始める~
beginPath()
- パラメータを指定して、直線や円弧などのパスを辿っていく~
lineTo()
など - それまでのパスの輪郭描画、塗りつぶしなどを行う~
stroke()
など
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<canvas id="cvs" width="400" height="150" style="background-color:silver"> </canvas> <script type="text/coffeescript"> jQuery ($) -> canvas = $("#cvs")[0] context = canvas.getContext("2d") if canvas.getContext <!-- 左に黒枠の正方形を描く/デフォルトの線の色は黒、太さ1 --> x = 20 y = 20 context.beginPath() context.moveTo(x, y) context.lineTo(x + 100, y) context.lineTo(x + 100, y + 100) context.lineTo(x, y + 100) context.stroke() <!-- 真ん中に赤枠の正方形を描く/線の色と幅を指定 --> x = 140 y = 20 context.strokeStyle = "#ff0000" context.lineWidth = 10 context.beginPath() context.moveTo(x, y) context.lineTo(x + 100, y) context.lineTo(x + 100, y + 100) context.lineTo(x, y + 100) context.closePath() context.stroke() <!-- 右に緑枠で青塗りつぶしの正方形を描く/塗りつぶしは線の中央が基準 --> x = 260 y = 20 context.strokeStyle = "#00ff00" context.fillStyle = "#0000ff" context.beginPath() context.moveTo(x, y) context.lineTo(x + 100, y) context.lineTo(x + 100, y + 100) context.lineTo(x, y + 100) context.closePath() context.stroke() context.fill() </script> |
実行結果
スタイル設定
色と背景
strokeStyle [= value]
- 線・輪郭の色やスタイルを指定。色指定の場合、red, blueなどの色名、”rgb(xx, xx, xx)”、”#dddddd”の3種類の指定方法。
fillStyle [= value]
- 塗りつぶしの色やスタイルを指定。
線のスタイル
lineWidth [= value]
- 線の幅を保持する。参照・設定可能。ゼロ以上有限の値のみ設定可能。初期値は1.0。
lineCap [= value]
- ラインキャップ(“butt”, “round”, “square”)を保持する。参照・設定可能。
lineJoin [= value]
- 線の接合方法(“bevel”, “round”, “miter”)を保持する。参照・設定可能。
miterLimit [= value]
- miter接合の場合の限界値を保持する。参照・設定可能。ゼロ以上有限の値のみ設定可能。初期値は10.0。
setLineDash([segments])
- 破線のパターンをセットする。パターンはリストで与え、
[実線(, 間隔, 実線, 間隔...)]
と記述。 getLineDash()
- 破線のパターンを返す。
lineDashOffset [= value]
- 破線パターンのオフセットを返す。
描画メソッド
矩形
strokeRect( x, y, w, h )
- 矩形の外枠を描く。
fillRect( x, y, w, h )
- 矩形の内側を塗りつぶす。塗りつぶしの基準となる外枠は、線の太さに関わらず線の中心線
clearRect( x, y, w, h )
- 指定した矩形の内部を背景色でクリアする。
canvasの描画は、ほとんどの場合線分、曲線、図形が繋がったパスとして描かれるが、矩形に関しては、パスとは独立した図形として描くメソッドが準備されている。
引数は4つで、左上のx座標、y座標、長方形の幅、高さ。
1 2 3 4 5 |
ctx.strokeRect( 50, 50, 250, 150 ); ctx.fillRect( 100, 100, 250, 150 ); // clearRect()は背景色で矩形型に消去 ctx.clearRect( 25, 150, 200, 100 ); |
実行結果
パス
パスの構成方法
パスは線分、円弧などの要素を連続して繋いだもので、パスの軌跡を描画するだけでなく、パスに対して内部を塗りつぶしたり、ある点がパスの内部にあるか判定したりすることができる。
パス描画の手順は、抽象的な構造を保存するパスに対して要素を追加していく段階と、それらに対して描画や塗りつぶしなどの操作を行う段階からなる。
パスの実装方法として、以下のいくつかがありうる。
- beginPath()による直接描画
beginPath()
メソッドで開始し、一連のパス追加などの操作の後、stroke()
メソッドで描画する。パスの再利用はできない。- 【例】画面中央に白抜きの三角形が描画される。
123456ctx.beginPath();ctx.moveTo( 50, 50 );ctx.lineTo( 350, 100 );ctx.lineTo( 200, 250 );ctx.closePath();ctx.stroke();
※begtinPath()
を書かないと、前の描画がリフレッシュされず全て残ってしまう。
※stroke()
を書かないとパスが描画されない。 - パス構築を関数とする方法
- パスの構築までを関数化し、その関数呼び出し後に描画する。関数を呼び出すことで、直前のパスとして再利用可能
- 【例】上記と同じ三角形が一旦白抜きで描かれた後、すぐに同じ三角形が黒で塗りつぶされて描かれる。
12345678910111213drawPath();ctx.stroke();drawPath();ctx.fill();function drawPath() {ctx.beginPath();ctx.moveTo( 50, 50 );ctx.lineTo( 350, 100 );ctx.lineTo( 200, 250 );ctx.closePath();} - Path2Dオブジェクトを用いる方法
- Path2Dオブジェクトのインスタンスにパスを追加し、これに対して描画する。パスオブジェクトが残るので、再利用可能。
- 【例】上記と同じく、白抜きの三角形の描画後に黒塗りの三角形が描画される。
12345678910var path = new Path2D();path.moveTo( 50, 50 );path.lineTo( 350, 100 );path.lineTo( 200, 250 );path.closePath();ctx.stroke( path );ctx.fill( path );
クリッピング
<略>
パス内部の判定
context.isPointInPath( x, y )
- 現在の設定されているサブパスに対して、点(x, y)が内部にあればtrue、外ならばfalseを返す
context.isPointInPath( path, x, y )
- pathで指定されたPath2Dオブジェクトに対して、天(x, y)が内部にあるかどうかを判定する
直接描画に対する判定の問題
1番目の表現の場合、直前に構成されたパスが判定対象となる。以下の例では二つの矩形を描いているが、内部判定は最後に書かれた矩形に対してのみなされる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var x1 = 100, y1 = 100; var x2 = 250, y2 = 180; ctx.beginPath(); ctx.rect( 50, 50, 150, 100 ); ctx.stroke(); ctx.beginPath(); ctx.rect( 180, 120, 150, 100 ); ctx.stroke(); ctx.fillRect( x1 - 5, y1 - 5, 10, 10 ); console.log( ctx.isPointInPath( x1, y1 ) ); // 結果はfalse // 最後に描かれた右下の矩形から見れば外側になるため ctx.fillRect( x2 - 5, y2 - 5, 10, 10 ); console.log( ctx.isPointInPath( x2, y2 ) ); // 結果はtrue // 最後に書かれた右下の矩形の内部にあるため |
パス構成の関数化
複数のパスに対して判定を繰り返す場合、stroke()/fill()
の描画メソッドを実行する前でもパスが構成されることを利用して、パス構成のための関数を切り分ける方法が考えられる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var x1 = 100, y1 = 100; var x2 = 250, y2 = 180; rect1( ctx ).stroke(); rect2( ctx ).stroke(); ctx.fillRect( x1 - 5, y1 - 5, 10, 10 ); ctx.fillRect( x2 - 5, y2 - 5, 10, 10 ); rect1( ctx );// 左上の長方形 console.log( ctx.isPointInPath( x1, y1 ) );// true console.log( ctx.isPointInPath( x2, y2 ) );// false rect2( ctx );// 右下の長方形 console.log( ctx.isPointInPath( x1, y1 ) );// false console.log( ctx.isPointInPath( x2, y2 ) );// true function rect1( ctx ) { ctx.beginPath(); ctx.rect( 50, 50, 150, 100 ); return ctx; } function rect2( ctx ) { ctx.beginPath(); ctx.rect( 180, 120, 150, 100 ); return ctx; } 上記のコードにより期待した結果は得られるが、判定の度にパス構成 |
Path2Dオブジェクトに対する判定
isPointInPath()
メソッドの2番目の表現は、第1引数にpathを渡すことができる。各々のパスをPath2Dオブジェクトに保持することで、よりシンプルに内部判定を行うことができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var x1 = 100, y1 = 100; var x2 = 250, y2 = 180; var rect1 = new Path2D(); var rect2 = new Path2D(); // 左上の矩形のパス構成 rect1.rect( 50, 50, 150, 100 ); // 右下の矩形のパス構成 rect2.rect( 180, 120, 150, 100 ); ctx.stroke( rect1 ); ctx.stroke( rect2 ); ctx.fillRect( x1 - 5, y1 - 5, 10, 10 ); ctx.fillRect( x2 - 5, y2 - 5, 10, 10 ); console.log( ctx.isPointInPath( rect1, x1, y1 ) );// true console.log( ctx.isPointInPath( rect1, x2, y2 ) );// false console.log( ctx.isPointInPath( rect2, x1, y1 ) );// false console.log( ctx.isPointInPath( rect2, x2, y2 ) );// true |
パスの要素
線分
- moveTo() 開始点に移動
- lineTo() 次の点まで線を描く~以下、繰り返し
- closePath() パスの終了時、開始点まで繋いでパスを閉じる場合に指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 左上、閉じたパスの例 ctx.beginPath(); ctx.moveTo( 20, 30 ); ctx.lineTo( 190, 50 ); ctx.lineTo( 150, 140 ); ctx.closePath(); ctx.stroke(); // 右上、閉じたパスを塗りつぶした例 ctx.beginPath(); ctx.moveTo( 20, 170 ); ctx.lineTo( 190, 190 ); ctx.lineTo( 150, 280 ); ctx.stroke(); // 左下、開いたパスの例 ctx.beginPath(); ctx.moveTo( 220, 30 ); ctx.lineTo( 390, 50 ); ctx.lineTo( 350, 140 ); ctx.closePath(); ctx.fill(); // 右下、開いたパスを塗りつぶした例 ctx.beginPath(); ctx.moveTo( 220, 170 ); ctx.lineTo( 390, 190 ); ctx.lineTo( 350, 280 ); ctx.fill(); |
実行結果
曲線
- quadraticCurveTo(…)
- bezierCurveTo(…)
<略>
円弧
arc( x, y, r, startAngle, endAngle, anticlockwise )
arcTo( x1, y1, x2, y2, r )
arcメソッドはパスの一部として円弧を描く。直前の座標軸からarcの開始点までと、arcの終了点から次の開始点なまでは線分が挿入される。
座標軸の正の方向が、x軸は右、y軸は下となっていることに注意。このため、一般的な数学表現と上限反転した体系になっていて、一般的な正の回転方向も時計回りとなる。
arcTo()メソッドの挙動は以下の通り。
- 直前の描画点(x0, y0)と(x1, y1)を結ぶ線分l1を想定する
- (x1, y1)と(x2, y2)を結ぶ線分l2を想定する
- 半径rでこの二つの線分に接する円弧を想定する(二つの直線の角をとるイメージ
- l1、l2と円弧の接点を算出する
- (x0, y0)~l1と円弧の接点を直線で結ぶ
- l1とl2を円弧で結ぶ
- l2の接点と次の描画開始点を直線で結ぶ
すなわち、(x2, y2)を次の描画開始点と一致させておけば、折れ線(x0, y0)~(x1, y1)~(x2, y2)の角が半径rの円弧で丸くなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 左上、真円の例 ctx.beginPath(); ctx.arc( 100, 80, 60, 0, Math.PI * 2, true ); ctx.stroke(); // 右上、0度~225度まで時計回りに描いた円弧 ctx.beginPath(); ctx.arc( 300, 80, 60, 0, 225 / 180 * Math.PI, false ); ctx.stroke(); // 左下、円弧を塗りつぶした例 ctx.beginPath(); ctx.arc( 100, 220, 60, 0, 270 / 180 * Math.PI, false ); ctx.fill(); // 右下、arcはパスの一部として扱われる ctx.beginPath(); ctx.arc( 300, 220, 60, 0, Math.PI, true ); ctx.arc( 300, 220, 40, Math.PI, 0, false ); ctx.arc( 300, 220, 20, 0, Math.PI, true ); ctx.stroke(); // arcToの例、直線の角が丸くなる ctx.beginPath(); ctx.moveTo( 250, 280 ); ctx.arcTo( 300, 280, 300, 200, 20 ); ctx.lineTo( 300, 230 ); ctx.stroke(); |
実行結果
矩形
rect( x, y, w, h )
rect()
はサブパスを持つ矩形を描く。
- 先行するパスとは接続しない
- 後続のパスがある場合、(x, y)から次の描画開始点まで線分で結ばれる
1 2 3 4 5 6 7 |
ctx.beginPath(); ctx.moveTo( 50, 50 ); ctx.lineTo( 150, 50 ); // この間は接続されず、新たに矩形が開始図形となる ctx.rect( 150, 150, 100, 50 ); ctx.lineTo( 300, 50 ); ctx.stroke(); |
実行結果