CanvasRenderingContext2D: arcTo() メソッド

CanvasRenderingContext2D.arcTo() はキャンバス 2D APIのメソッドで、制御点と半径を指定して現在のサブパスに円弧を追加します。 円弧は、例えば始点と制御点が一直線上にある場合など、必要に応じてパスの最新点に自動的に直線で接続されます。

このメソッドは、主に角丸の図形を描画するのに使用されます。

メモ: 相対的に大きな半径を指定した場合、得られる角丸の描線が期待するものとは異なる可能性があります。円弧と連結する直線は円弧の半径に合うように描線されます。

構文

js
arcTo(x1, y1, x2, y2, radius)

引数

x1

1 つ目の制御点の x 座標。

y1

1 つ目の制御点の y 座標。

x2

2 つ目の制御点の x 座標。

y2

2 つ目の制御点の y 座標。

radius

円弧の半径。負でない値を設定する必要があります。

使用上のメモ

P0arcTo() が呼び出されたときのパス上の点、 P1 = (x1, y1) と P2 = (x2, y2) はそれぞれ 1 つ目と 2 つ目の制御点、 r は名付けで指定した 半径 であると想定します。

  • r が負の場合、IndexSizeError 例外が発生します。
  • もし r が 0 ならば、 arcTo() は、P0, P1, P2 が(行の中で)平行であるかのように振る舞います。
  • これらの点のすべてが一直線に並ぶ場合、 P0 から P1 への線が描画されます。ただし、 P0P1 が一致する(同じ座標である)場合は何も描画されません。

これらの条件は、下記の arcTo() パスの作成 の例で作成し、結果を見ることができますs。

返値

なし (undefined)。

例外

IndexSizeError DOMException

radius が負の値の場合に発生します。

arcTo() の動作

arcTo() の動作を解釈するには、始点と 1 つ目の制御点を結ぶ直線と、そこから 2 つ目の制御点を結ぶ直線の 2 つの線をイメージする方法があります。 arcTo() を使用しない場合、これら 2 つの線分は鋭角を形成しますが、 arcTo() はこの鋭角部分に接する円弧を描くことで滑らかに接続します。すなわち、 2 つの直線に接する円弧を作成することになります。

HTML

html
<canvas id="canvas"></canvas>

JavaScript

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// 補助線
ctx.beginPath();
ctx.strokeStyle = "gray";
ctx.moveTo(200, 20);
ctx.lineTo(200, 130);
ctx.lineTo(50, 20);
ctx.stroke();

// 円弧
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.moveTo(200, 20);
ctx.arcTo(200, 130, 50, 20, 40);
ctx.stroke();

// 開始点
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(200, 20, 5, 0, 2 * Math.PI);
ctx.fill();

// 制御点
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(200, 130, 5, 0, 2 * Math.PI); // 制御点 1
ctx.arc(50, 20, 5, 0, 2 * Math.PI); // 制御点 2
ctx.fill();

結果

この例では、 arcTo() によって描かれる線を黒い太線を描きます。補助線は灰色、制御点は赤、開始点は青です。

角丸図形の作成

この例では arcTo()を用いて丸い角を持つ線を描画しています。これが最も一般的な使われ方です。

HTML

html
<canvas id="canvas"></canvas>

JavaScript

描線は直前の moveTo() により座標 (230, 20) から開始し、 2 つの制御点 (90, 130) と (20, 20) を結ぶ直線に接するように形成された半径 50 の円弧に接続されます。円弧の終端からは lineTo() メソッドにより (20, 20) の点まで直線が描画されます。 2 つ目の制御点の座標と同じ座標を lineTo() で指定することで、より滑らかな描線を得ることができます。

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const p0 = { x: 230, y: 20 };
const p1 = { x: 90, y: 130 };
const p2 = { x: 20, y: 20 };

const labelPoint = (p) => {
  const offset = 10;
  ctx.fillText(`(${p.x},${p.y})`, p.x + offset, p.y + offset);
};

ctx.beginPath();
ctx.lineWidth = 4;
ctx.font = "1em sans-serif";
ctx.moveTo(p0.x, p0.y);
ctx.arcTo(p1.x, p1.y, p2.x, p2.y, 50);
ctx.lineTo(p2.x, p2.y);

labelPoint(p0);
labelPoint(p1);
labelPoint(p2);

ctx.stroke();

結果

大きい半径を指定した場合

相対的に大きな半径を指定した場合には、前述の方法では期待されるような滑らかな描線は得られません。この例では、 moveTo() 後の地点から円弧に接続される線は下方ではなく上方に向かってしまっています。これは、 2 つの直線に接する円の半径が大きすぎるために、始点よりも上方に円弧との接点があるために発生しています。

HTML

html
<canvas id="canvas"></canvas>

JavaScript

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(180, 90);
ctx.arcTo(180, 130, 110, 130, 130);
ctx.lineTo(110, 130);
ctx.stroke();

結果

arcTo() のパスを構築

このデモでは、 arcTo() で描画されるパスを決定するために使用する、 T1T2 での線に接する半直線と中心 C を持つ円を示しています。

arcTo は、すべての点が直線上にある場合、 P0 から P1 までの直線を作成することに注意してください。また、 P0P1 に同じ座標がある場合、arcTo では何も描画されません。

弧の半径をスライダーで設定することができるほか、初期点 P0、制御点 P1P2 は、左ボタンを押したままマウスでドラッグすることで移動させることができます。数値の編集もできますし、矢印キーを使用して、フォーカスされている下線要素を変更することもできます。

arcTo() の描画のアニメーション

この例では、円弧の半径を変更して、パスの変化を見ることができます。パスは、arcTo() を使用して開始点 p0 から、制御点 p1 および p2 と、スライダーで選択した 0 から最大半径まで変化する半径を使用して描画されます。その後、 lineTo() を呼び出すと、p2 までのパスが完全に完成します。

HTML

html
<div>
  <label for="radius">半径: </label>
  <input name="radius" type="range" id="radius" min="0" max="100" value="50" />
  <label for="radius" id="radius-output">50</label>
</div>
<canvas id="canvas"></canvas>

JavaScript

js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const controlOut = document.getElementById("radius-output");
const control = document.getElementById("radius");
control.oninput = () => {
  controlOut.textContent = radius = control.value;
};

const p1 = { x: 100, y: 100 };
const p2 = { x: 150, y: 50 };
const p3 = { x: 200, y: 100 };
let radius = control.value; // match with init control value

function labelPoint(p, offset, i = 0) {
  const { x, y } = offset;
  ctx.beginPath();
  ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillText(`${i}:(${p.x}, ${p.y})`, p.x + x, p.y + y);
}

function drawPoints(points) {
  points.forEach((p, i) => {
    labelPoint(p, { x: 0, y: -20 }, `p${i}`);
  });
}

// Draw arc
function drawArc([p0, p1, p2], r) {
  ctx.beginPath();
  ctx.moveTo(p0.x, p0.y);
  ctx.arcTo(p1.x, p1.y, p2.x, p2.y, r);
  ctx.lineTo(p2.x, p2.y);
  ctx.stroke();
}

function loop(t) {
  const angle = (t / 1000) % (2 * Math.PI);
  const rr = Math.abs(Math.cos(angle) * radius);

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  drawArc([p1, p2, p3], rr);
  drawPoints([p1, p2, p3]);
  requestAnimationFrame(loop);
}

loop(0);

結果

仕様書

Specification
HTML Standard
# dom-context-2d-arcto-dev

ブラウザーの互換性

BCD tables only load in the browser

関連情報