はじめに (対象読者・この記事でわかること)
このページは、JavaScriptでブラウザ上に描画できるCanvas APIに興味があるWebフロントエンド初心者から中級者を対象にしています。
Canvas の基本的な描画手法は把握しているが、Path オブジェクトを使った複雑な形状や連続描画に自信がない、という方が対象です。
この記事を読むことで、beginPath(), moveTo(), lineTo(), arc(), closePath() などの主要メソッドの意味と実装パターンが分かり、自由曲線やベジェ曲線、クリッピング領域を組み合わせた高度な図形をコード一本で描けるようになります。さらに、アニメーションに活かすテクニックや、よく陥りがちなエラーの対処法も併せて紹介します。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- HTML と CSS の基本的な知識
- JavaScript(ES6 以降)の基礎文法
- ブラウザのデベロッパーツールの使い方
CanvasとPathの基本概念
HTML5 で導入された <canvas> 要素は、ビットマップベースの描画領域を提供しますが、描画命令は 2D コンテキスト ( canvas.getContext('2d') ) に対して行います。2D コンテキストは、線や塗りつぶし、画像の描画など多彩な API を持っていますが、その中心に位置するのが Path です。
Path は「点と線・曲線の集合体」であり、描画対象を一時的に蓄積し、stroke()(線描画)や fill()(塗りつぶし)で一括して描画します。Path の利点は次のとおりです。
- 状態管理がシンプル:
beginPath()で新規パスを開始し、必要な座標や曲線を順次追加できるため、描画ロジックが分離しやすい。 - 再利用が容易:同一の Path オブジェクトを保存し、
save()/restore()と組み合わせると、同じ形状を異なるスタイルで何度でも描画できる。 - クリッピングや合成が可能:
clip()で描画領域を限定したり、globalCompositeOperationと組み合わせて高度な合成効果が実現できる。
代表的な Path メソッドを簡単に整理すると次のようになります。
| メソッド | 説明 |
|---|---|
beginPath() |
新しい Path を開始。既存のパスはリセットされる。 |
moveTo(x, y) |
ペンを指定座標へ移動(線は描かれない)。 |
lineTo(x, y) |
現在位置から指定座標へ直線を追加。 |
arc(x, y, radius, startAngle, endAngle, anticlockwise) |
円弧(円も可)を追加。ラジアンで角度指定。 |
quadraticCurveTo(cpX, cpY, x, y) |
2 次ベジェ曲線。制御点と終点を指定。 |
bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, x, y) |
3 次ベジェ曲線。2 つの制御点と終点を指定。 |
closePath() |
パスの開始点に線を引き、閉じた形状にする。 |
stroke() / fill() |
パスを線描画または塗りつぶしで描画。 |
これらを組み合わせるだけで、星形・ハート・ロゴのような自由曲線 から、円形ローディングアニメーション まで幅広い表現が可能です。次の章では、実際にコードを書きながら具体的な手順を追っていきます。
Pathを使った実践的な図形描画とアニメーション
ここからは、Canvas の Path 機能を駆使した「ハート形と回転アニメーション」を例に、最初から最後まで実装手順を解説します。コードは ES6 構文で記述し、ブラウザの互換性は最新の Chrome / Edge / Safari を前提としますが、基本的には全てのモダンブラウザで動作します。
ステップ1 HTML と Canvas の準備
まずは描画領域を作ります。width と height は CSS ではなく属性で決めることがポイントです。属性で指定しないと、内部のピクセルサイズがデバイスピクセル比に依存し、ぼやけた描画になることがあります。
Html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Canvas Path ハートアニメーション</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin:0; background:#f0f0f0; } canvas { background:#fff; box-shadow:0 0 10px rgba(0,0,0,0.2); } </style> </head> <body> <canvas id="myCanvas" width="400" height="400"></canvas> <script src="main.js"></script> </body> </html>
ステップ2 Path でハート形を描く関数を作成
ハート形は 2 つの円弧と 1 本のベジェ曲線で構成できます。座標は Canvas の中心 (200,200) を基準に相対的に設定します。
Js// main.js const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); // デバイスピクセル比対応(オプション) function fixDPR() { const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); } fixDPR(); /** * ハート形のパスを作成し、現在のパスに追加する * @param {CanvasRenderingContext2D} ctx * @param {number} x 中心X * @param {number} y 中心Y * @param {number} size スケール(半径相当) */ function drawHeartPath(ctx, x, y, size) { ctx.beginPath(); // 左上の円弧 ctx.arc(x - size / 2, y - size / 4, size / 2, Math.PI, 0, true); // 右上の円弧 ctx.arc(x + size / 2, y - size / 4, size / 2, Math.PI, 0, true); // 下側のベジェ曲線で合体 ctx.bezierCurveTo( x + size, y + size / 2, // 制御点1 x - size, y + size / 2, // 制御点2 x, y + size // 終点 ); ctx.closePath(); }
ステップ3 描画とアニメーションロジック
requestAnimationFrame を利用して、ハートを回転させながら描画します。回転はコンテキストの translate と rotate を駆使します。
Jslet angle = 0; // 現在の回転角(ラジアン) const size = 80; // ハートのサイズ function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 回転の中心をキャンバス中心に設定 ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(angle); // ハートを描画(中心を (0,0) に合わせている点に注意) drawHeartPath(ctx, 0, 0, size); ctx.fillStyle = '#ff69b4'; ctx.fill(); // 塗りつぶし ctx.strokeStyle = '#c71585'; ctx.lineWidth = 2; ctx.stroke(); // 線描画 ctx.restore(); // 角度を更新(毎フレーム 0.02 ラジアン回転) angle += 0.02; requestAnimationFrame(render); } render();
ハマった点やエラー解決
1. パスがずれて描画される
- 原因:
translate()の座標がキャンバスの CSS サイズ(CSS ピクセル)と実際の描画サイズ(ピクセル)で食い違っていた。 - 対処:
fixDPR()関数でcanvas.widthとcanvas.heightをデバイスピクセル比に合わせ、ctx.scale(dpr, dpr)で座標系を統一した。
2. arc() が期待通りの形にならない
- 原因:角度をラジアンで指定する必要があるが、度数(degree)で記述していた。
- 対処:
Math.PI * (deg / 180)のように変換し、すべての角度をラジアンで統一した。
3. requestAnimationFrame が遅延する
- 原因:他の重いスクリプトが同じスレッドで実行され、メインスレッドがブロックされていた。
- 対処:不要な
console.logを削除し、画像のプリロードやデータ取得はPromise/asyncに分離した。
解決策のまとめ
| 問題 | 原因 | 解決策 |
|---|---|---|
| 描画がぼやける | DPR 非対応 | fixDPR() でピクセル比を合わせる |
| パス位置がずれる | translate の基準が違う |
ctx.translate(canvas.width/2, canvas.height/2) 前にスケール補正 |
| 角度指定ミス | 度数 vs ラジアン | 常に Math.PI 系で角度計算 |
| アニメーションがカクつく | メインスレッド負荷 | ロジック分離・console.log削除、必要なら Web Worker 利用 |
以上のポイントを抑えておけば、Canvas の Path を用いた 高品質かつ高速な描画 が実現できます。さらに応用すれば、パス同士の合成(addPath)やクリッピング、テキストと画像のマスク など、よりリッチな UI 表現も可能です。
まとめ
本記事では、Canvas の Path 機能を体系的に学び、ハート形の描画と回転アニメーションを実装する手順 を紹介しました。
- Path の基本メソッド(
beginPath,moveTo,lineTo,arc,bezierCurveTo,closePath)を理解し、状態管理をシンプルに保つ方法。 - 実装例としてハート形を作成し、
translate/rotateと組み合わせたアニメーションを構築。 - ハマりやすいポイント(DPR 対応、ラジアン変換、メインスレッドの負荷)とその具体的な解決策。
これにより、読者は Canvas の Path を使いこなして自由曲線やベジェ曲線を描き、動的表現に応用できる というスキルを身につけました。次回は「Path と clip() を組み合わせた複雑なマスク効果」や「オフスクリーン Canvas を活用したパフォーマンス最適化」について掘り下げていく予定です。
参考資料
- MDN Web Docs – CanvasRenderingContext2D
- HTML5 Canvas の Path とベジェ曲線まとめ
- 「HTML5 Canvas アートブック」 (著者: 小林 俊)