【STEP.39】
Canvas APIの使い方をマスターしよう!

Canvas APIとは

Canvas APIを利用することで、HTML+JavaScriptだけで特定の領域に対して図形を描画できます。

従来、ブラウザーに動的に図形を描画するにはFlashのようなプラグインを利用する必要がありました。

しかし、Canvas APIを利用することで、ブラウザー標準の機能だけで同じようなことが実現できます。

たとえば以下は、図形描画のためのキャンバスを用意する例です。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>

Canvas APIを利用するには、まず<canvas>要素で図形を描画する領域(キャンバス)を定義します。

heightwidth属性は、キャンバスの高さ/幅を表します。

スタイルシートのheightwidthプロパティは、<canvas>要素のheightwidth属性で指定されたサイズを拡大/縮小する意味になってしまうため、利用すべきではありません。

<canvas>要素配下では、Canvas APIを利用できないブラウザーで表示するコンテンツです。

サンプルではエラーメッセージを表示するだけですが、一般的には、キャンバスで描画している図形を代替するコンテンツ(グラフであれば、その元データなど)を記述しておくのが望ましいでしょう。

四角形を描画してみよう

キャンバスに四角形を描画するには、fillRectstrokeRectメソッドを利用します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d', { alpha: true });
// 矩形を描画
c.strokeRect(50, 20, 200, 200);
c.fillRect(180, 80, 100, 100);

キャンバスに矩形を描画

キャンバスに図形を描画するには、まずgetContextメソッドでコンテキストオブジェクトを生成します。(let c = cv.getContext('2d', { alpha: true });

コンテキストオブジェクトは、キャンバスに対する「絵筆」の役割を担います。

getContextメソッド

canvas.getContext(type, opts)
type    コンテキストの種類
opts    動作オプション

引数type2dとしているのは、「2次元画像を描画するための絵筆を準備せよ」という意味です。

その他にも、ブラウザーの実装によってはwebglwebgl2(3次元コンテキスト)を指定することもできます。

引数optsで利用できるオプションは、引数typeによって変化します。

2dであれば、利用できるのはalpha(キャンバスで半透明効果を利用するかどうか)だけです。

キャンバスで半透明効果を利用していない場合にはfalseを指定することで、描画を最適化できます。

コンテキストを取得できたら、あとは、それ経由でstrokeRectメソッドを呼び出すことで枠線のみの矩形を、fillRectメソッドで塗りつぶされた矩形を、それぞれ描画します。(上のJavaScriptのコード5~6行目)

キャンバスでは左上を(0,0)とし、水平方向がX軸、垂直方向Y軸となります。

fillRect/strokeRectメソッド

context.fillRect(x, y, width, height)
context.strokeRect(x, y, width, height)
x       左上のX座標
y       左上のY座標
width   幅
height  高さ

直線を描画してみよう

パス(Path)を利用します。

パスとは、コンテキストで管理される座標集合のことです。

Canvas APIでは、パスで管理された座標によって、直線や曲線を描画、あるいは該当の領域を塗りつぶすことになります。

以下は、パスを利用して直線を描画する例です。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath(); // パスの開始
c.moveTo(300, 20); // 基点
c.lineTo(50, 200); // 終点
c.stroke(); // 描画

キャンバス上に直線を描画

パスを利用するにあたっては、まずbeginPathメソッドで開始を宣言します。(c.beginPath();

すでに座標(群)が指定されていた場合には、これをクリアします。

続いて、moveToメソッドでパスの視点を、lineToメソッドで直線の終点を、それぞれ指定します。(c.moveTo(300, 20);c.lineTo(50, 200);

これで座標の設定ができたので、最後にstrokeメソッドを呼び出すことで、パスに基づいて直線が描画されます。(c.stroke();

パスの定義だけでは、描画そのものはされませんので要注意です。

以下のようにlineToメソッドを連ねることもできます。

この場合、一連の座標群に沿って折れ線を描画します。

let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath();
c.moveTo(300, 20);
c.lineTo(50, 200);
c.lineTo(150, 200);
c.lineTo(200, 150);
c.lineTo(300, 220);
c.stroke();

パスに沿って折れ線を描画

多角形を描画してみよう

パス定義の最後で、closePathメソッドを呼び出します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath();
c.moveTo(300, 20);
c.lineTo(50, 200);
c.lineTo(150, 200);
c.lineTo(200, 150);
c.lineTo(300, 220);
c.closePath();
c.stroke();

パスに沿って多角形を描画

c.closePath();」によってパスの始点と終点が結ばれます。

このようなパスのことをクローズパスといいます。

対して、始点と終点が結ばれない(開かれた)パスのことをオープンパスと呼びます。

サンプルではstrokeメソッドを呼び出していますので、枠線のみの図形を描画していますが、図形の内部を塗りつぶしたいならば、fillメソッドを呼び出してください。

以下は、「c.fill();」で置き換えた場合の結果です。

塗りつぶし図形の描画

グラデーション効果を適用してみよう

strokeStylefillStyleプロパティには、グラデーション(CanvasGradientオブジェクト)を指定することもできます。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath();
// グラデーションを定義
let grad = c.createLinearGradient(15, 15, 280, 130);
grad.addColorStop(0, '#0f0');
grad.addColorStop(0.6, '#ff0');
grad.addColorStop(1, '#f0f');
c.fillStyle = grad;
c.moveTo(300, 20);
c.lineTo(50, 200);
c.lineTo(150, 200);
c.lineTo(200, 150);
c.lineTo(300, 220);
c.stroke();
c.fill();

グラデーションで塗りつぶし

createLinearGradientメソッドは、線形グラデーションを表すCanvasGradientオブジェクトを生成します。(let grad = c.createLinearGradient(15, 15, 280, 130);

線形グラデーションとは、左から右、上から下など、指定の方向に線上に変化するグラデーションのことです。

createLinearGradientメソッド

context.createLinearGradient(x1, y1, x2, y2)
x1    始点のX座標
y2    始点のY座標 
x2    終点のX座標
y2    終点のY座標

これによって、(x1,y1)から(x2,y2)に変化する線形グラデーションを生成します。

ただし、これだけではグラデーションの始点/終点が定義されたにすぎませんので、変化の度合い、色を決める必要があります。

これを行うのが、addColorStopメソッドです。(上のJavaScriptのコード7~9行目)

addColorStopメソッド

context.addColorStop(offset, color)
offset   オフセット
color    色

オフセットは色を変化させる位置を、0.0(始点)~1.0(終点)で表します。

サンプルであれば#0f0で始まり、中間点(0.6)で#ff0を経て、最後に#f0fとなるようなグラデーションを表します。

生成されたグラデーションは、そのままfillStylestrokeStyleメソッドにセットできます。(c.fillStyle = grad;

円形グラデーションを適用してみよう

中心(x1,y1)、半径r1の円から中心(x2,y2)、半径r2の円に向けた円形グラデーションを定義することもできます。

これには、createRadialGradientを利用してください。

以下は、let grad = c.createLinearGradient(15, 15, 280, 130);createRadialGradientメソッドで書き換えた例です。

中心(150,150)、半径5の円から中心(200,200)、半径110の円に向けたグラデーションを表します。

let grad = c.createRadialGradient(150, 150, 5, 200, 200, 110);
grad.addColorStop(0, '#0f0');
grad.addColorStop(0.6, '#ff0');
grad.addColorStop(1, '#f0f');

2円の間を徐々に変化する円形グラデーション

円・円弧を描画してみよう

arcメソッドを利用します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath();
c.arc(200, 125, 100, 0, 2 * Math.PI, false);
c.stroke();

円を描画

arcメソッドの構文は、次の通りです。

arcメソッド

context.arc(x, y, radius, startAngle, endAngle, anticlock)
x           中心のX座標
y           中心のY座標
radius      半径
startAngle  開始角度
endAngle    終了角度
anticlock   反時計回りにするかどうか(true/false)

開始/終了角度は右水平方向を基点に、時計回りの方向に指定します。

単位はラジアンです。

ラジアンは「度数÷180×π」で求められます。

円周率πはMath.PIで取得できます。

arcメソッドは、指定の角度に基づいて、時計回りに円弧を描画します。

もしも引数anticlocktrueをした場合には、反時計回りに円弧を描画します。

開始~終了角度が360°以上であれば、arcメソッドは完全な円を、さもなくば円弧を描画します。

ベジェ曲線を描画してみよう

ベジェ曲線とは、n個の制御点に基づいて描かれるn-1次曲線です。

CanvasAPIでは、次のような2次、3次ベジェ曲線に対応しています。

ベジェ曲線

以下に、具体的なコードを示します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
// 2次ベジェ曲線を描画
c.beginPath();
c.moveTo(50, 50);
c.quadraticCurveTo(125, 200, 200, 50);
c.stroke();
	
// 3次ベジェ曲線を描画
c.beginPath();
c.moveTo(150, 175);
c.bezierCurveTo(200, 300, 300, 10, 350, 175);
c.stroke();

曲線を描画

ベジェ曲線を定義するのは、それぞれquadraticCurveTobezierCurveToメソッドの役割です。

引数x1、y1…は上図の座標を、x0、y0はmoveToメソッドの座標を、それぞれ表すものとします。

quadraticCurveTo/bezierCurveToメソッド

context.quadraticCurveTo(x1, y1, x2, y2)
context.bezierCurveTo(x1, y1, x2, y2, x3, y3)

キャンバスにテキストを描画してみよう

キャンバスに文字列を埋め込むには、strokeTextfillTextメソッドを利用します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.strokeStyle = '#000'; // 描画色
c.fillStyle = '#0f0'; // 塗りつぶし
c.font = 'italic bold 40px serif'; // フォント
c.textAlign = 'right'; // 水平方向位置
c.fillText('JavaScript', 400, 100);
c.strokeText('JavaScript', 380, 170);

キャンバスにテキストを描画

strokeTextfillTextメソッドは、それぞれ指定された座標にテキストを描画します。

両者の違いは、枠線のみのテキストを描画するか、塗りつぶしテキストを描画するか、です。

strokeText/fillTextメソッド

context.strokeText(text, x, y)
context.fillText(text, x, y)
text  テキスト
x     始点のX座標
y     始点のY座標

文字の表示位置は、引数xyと、textAlignプロパティによって決まります。

textAlignプロパティはテキストの横位置を表し、left、right、center、start、endなどの値を指定できます。

サンプルではrightを指定していますので、指定されたx座標がテキストの終点(右隈)となるように、テキストが配置されます。

textAlignプロパティによる水平位置の指定

fontプロパティで、フォントを指定することもできます。

形式はCSSのそれと同じで、font-style、font-weight、font-size、font-familyの順で指定できます。(font-size、font-familyのみ必須です。)

MEMO
start/endの意味
start/endは、スケール(地域情報)によって挙動が変化します。

日本語環境ではそれぞれleft/rightと同じ意味ですが、地域によっては右左方向に文字を並べる言語があります。

そのような言語では、start/endはそれぞれright/leftの意味になります。(それぞれの並びに応じて、始端/終端に寄せるわけです。)

国際化対応を意識するならば、left/rightよりもstart/endはを利用すべきです。

キャンバスに画像を埋め込んでみよう

drawImageメソッドを利用します。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
let img = new Image();
img.setAttribute('src', 'https://bit.ly/3fQdXNc');
img.addEventListener('load', function () {
  c.drawImage(this, 40, 40, 250, 160);
}, false);

キャンバスに画像を貼り付け

drawImageメソッドを利用するためには、まずImageオブジェクトで対象の画像を読み込んでおく必要があります。(img.setAttribute('src', './image/canvas.jpg');

ただし、画像は非同期に読み込まれる点に注意してください。

つまり、loadイベントで画像のロードが完了したところで、drawImageメソッドを呼び出さなければなりません。(c.drawImage(this, 80, 80, 250, 160);

drawImageメソッドの構文は以下の通りです。

drawImageメソッド

context.dwarImage(image, x, y, width, height)
image   Imageオブジェクト
x       X座標
y       Y座標
width   幅
height  高さ 

これで指定の画像を、(X,Y)座標に幅×高さにリサイズしたうえで、キャンバスに貼り付けます。

リサイズの必要がない場合には、幅、高さは省略してもかまいません。

画像の一部を切り取ってみよう

drawImageメソッドの第2構文を利用することで、画像の一部を切り出すこともできます。

drawImageメソッド(第2構文)

context.drawImage(image, x1, y1, width1, height1, x2, y2, width2, height2)
image    Imageオブジェクト
x1       切り取り開始のX座標
y1       切り取り開始のY座標
width1   切り取る幅
height1  切り取る高さ
x2       キャンバス上のX座標 
y2       キャンバス上のY座標
width2   リサイズの幅
height2  リサイズの高さ

試しに、上のサンプルの「c.drawImage(this, 40, 40, 250, 160);」を以下のように書き換えてみましょう。

c.drawImage(this, 80, 80, 400, 100, 100, 100, 400, 100);

画像の一部を拡大して貼り付け

特定の領域に沿って画像を切り抜いてみよう

clipメソッドを呼び出すことで、キャンバスの特定の領域だけをくりぬけます(クリッピング領域)。

たとえば以下は、arcメソッドで定義した円に沿って、画像を張り付ける例です。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
c.beginPath();
c.arc(230, 130, 90, 0, 2 * Math.PI, false);
c.stroke();
c.clip();
let img = new Image();
img.setAttribute('src', 'https://bit.ly/3fQdXNc');
img.addEventListener('load', function () {
  c.drawImage(this, 0, 0, 400, 300);
}, false);

クリッピング領域内部に画像を貼り付け

clipメソッドを呼び出すことで、以降は、クリッピング領域の中に対してのみ描画される点に注目です。

画像を縦・横方向に繰り返し貼り付けてみよう

strokeStylefillStyleプロパティには、画像パターンを指定することもできます。

パターンとは、図形(やその枠線)を塗りつぶす際に利用する、画像の繰り返しのことです。

パターンを利用することで、画像を背景のように敷き詰めることもできます。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
let img = new Image();
img.setAttribute('src', 'https://bit.ly/2T8seuN');
img.addEventListener('load', function () {
  // 読み込んだ画像をもとにパターンを生成&塗りつぶし色として設定
  c.fillStyle = c.createPattern(this, 'repeat');
  c.fillRect(0, 0, 400, 250);
}, false);

画像を背景として敷き詰める

パターン(CanvasPatternオブジェクト)は、createPatternメソッドで作成できます。

パターン種別には、repeat(上下左右に繰り返し)の他、repeat-xrepeat-y(水平方向、垂直方向にのみ繰り返し)、no-repeat(繰り返さない)を指定できます。

createPatternメソッド

context.createPattern(image, repetition)
image       Imageオブジェクト
repetition  パターン種別

あとは、生成されたCanvasPatternオブジェクトをfillStyleプロパティにセットするだけです。

キャンバスそのものに背景を設置することはできませんので、fillRectメソッドでキャンバスと同じ大きさの矩形を描画しています。

画像を拡大/回転/移動/変形してみよう

Canvas APIでは、scale(サイズ変更)、rotate(回転)、translate(移動)、transform(変形)のようなメソッドが用意されており、図形を加工できます。

以下は、その具体的な例です。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvas機能に対応していません。
</canvas>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
// 初期状態のキャンバスを3個保存
for (let i = 0; i < 3; i++) {c.save();}
let img = new Image();
img.setAttribute('src', 'https://bit.ly/3bwux11');
img.addEventListener('load', function () {
  // 60度回転したものを描画
  c.rotate(60 * Math.PI / 180);
  c.drawImage(this, 0, 0,);

  c.restore();
  // 45%に縮小して描画
  c.scale(0.45, 0.45);
  c.drawImage(this, 80, 50);

  c.restore();
  // (180, 20)ずらしたうえで描画
  c.translate(180, 20);
  c.drawImage(this, 0, 0);
		
  c.restore();
  // 変形マトリックスで変換したうえで描画
  c.transform(1, 1, 1, -1, 0, 0);
  c.drawImage(this, 150, 10);
}, false);

さまざまに変形した画像

それぞれのメソッドの構文は、以下の通りです。

scale/rotate/translate/transformメソッド

context.scale(x, y)
context.rotate(angle)
context.translate(dx, dy)
context.transform(m11, m12, m21, m22, dx, dy)
x      水平倍率
y      垂直倍率
angle  角度
dx     水平移動距離
dy     垂直移動距離

transformメソッドは、その他の加工メソッドをまとめた汎用的なメソッドで、次のような変換マトリックスを使って図形を変形します。

変換マトリックス

次に掲げる表に、scalerotatetranslateメソッドに対応したtranslateメソッドの記述例を示します。(ただし、単純なサイズ変更/回転/移動であれば、専用のメソッドを利用すべきです。)

【translateメソッドの主なメソッド】
記述例意味
translate(x, 0, 0, y, 0, 0,)横x倍/縦y倍の拡大
translate(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cons(r), 0, 0)r(ラジアン)だけ回転
translate(1, 0, 0, 1, x, y)水平方向x/垂直方向yの移動

これらのメソッドはすべて、実行都度に累積されます。

そのため(累積ではなく)新たに変形を適用したい場合は、restoreメソッドを呼び出して、キャンバスをもとに戻すようにしてください。

キャンバスの内容をData URL形式で出力してみよう

キャンバスで描画した画像は、toDataURLメソッドを利用すことで、Data URL形式で出力できます。

たとえば以下は、キャンバスの内容をData URL形式に変換し、そのデータに基づいて、新たに<img>タグを生成する(=キャンバスの内容をコピーする)サンプルです。

<canvas id="cv" width="400" height="250">
  ご利用の環境は、Canvasに対応していません。
</canvas>
<input id="copy" type="button" value="模写" />
<div id="result"></div>
let cv = document.getElementById('cv');
// コンテキストオブジェクトを生成
let c = cv.getContext('2d');
// 元となる図形を描画
c.beginPath();
c.arc(230, 130, 90, 0, 2 * Math.PI, false);
c.stroke();
c.clip();
let img = new Image();
img.setAttribute('src', './images/image-1.jpg'); 
img.addEventListener('load', function () {
 c.drawImage(this, 0, 0, 400, 300)
}, false);
	
// [複写]ボタンでキャンバスの内容をコピー
let copy = document.getElementById('copy');
copy.addEventListener('click', function () {
  let img2 = new Image();
  // Data URL形式のパスをsrc属性にセット
  img2.setAttribute('src', cv.toDataURL());
  img2.addEventListener('load', function () {
    document.getElementById('result').appendChild(this);
  }, false);
}, false);

キャンバスの内容を<img><noscript><img src=

ここでは、取得したData URLをsrcプロパティに設定しているだけですが、もちろんWeb Storageやデータベースなどに保存することもできます。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です