はじめに (対象読者・この記事でわかること)

このページは、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 の利点は次のとおりです。

  1. 状態管理がシンプルbeginPath() で新規パスを開始し、必要な座標や曲線を順次追加できるため、描画ロジックが分離しやすい。
  2. 再利用が容易:同一の Path オブジェクトを保存し、save()/restore() と組み合わせると、同じ形状を異なるスタイルで何度でも描画できる。
  3. クリッピングや合成が可能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 の準備

まずは描画領域を作ります。widthheight は 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 を利用して、ハートを回転させながら描画します。回転はコンテキストの translaterotate を駆使します。

Js
let 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.widthcanvas.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 を活用したパフォーマンス最適化」について掘り下げていく予定です。

参考資料