CanvasRenderingContext2D: arcTo() 方法

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

Canvas 2D API 的 CanvasRenderingContext2D.arcTo() 方法使用给定的控制点和半径,在当前子路径上添加一个圆形弧线。如果需要,弧线会自动通过直线连接到路径的最新点,例如,如果起点和控制点在一条直线上。

此方法常用于制作圆角。

注意: 使用相对较大的半径可能会得到意想不到的结果:弧线的连接线将以任何必要的方式延伸以满足指定的半径。

语法

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

参数

x1

第一个控制点的 x 轴坐标。

y1

第一个控制点的 y 轴坐标。

x2

第二个控制点的 x 轴坐标。

y2

第二个控制点的 y 轴坐标。

半径

弧线的半径。必须为非负数。

用法说明

假设P0 是调用arcTo()时路径上的点,P1 = (x1, y1) 和P2 = (x2, y2) 分别是第一个和第二个控制点,而r 是调用中指定的radius

  • 如果r 为负数,则会抛出IndexSizeError 异常
  • 如果r 为 0,arcTo() 的行为就像P0P1P2 共线(在一条直线上)。
  • 在所有点共线的情况下,会绘制一条从P0P1 的直线,除非点P0P1 重合(具有相同的坐标),在这种情况下,将不绘制任何内容。

这些条件可以在下面的 Constructing an arcTo() path 示例中创建,以查看结果。

返回值

无(undefined)。

异常

IndexSizeError DOMException

如果 radius 是负值,则会抛出此异常。

示例

arcTo() 的工作原理

理解arcTo() 的一种方式是想象两条直线段:一条从起点到第一个控制点,另一条从那里到第二个控制点。如果没有arcTo(),这两条线段会形成一个尖角:arcTo() 在这个角处创建一个圆弧并使其平滑。换句话说,圆弧与这两条线段都相切。

HTML

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

JavaScript

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

// Tangential lines
ctx.beginPath();
ctx.strokeStyle = "gray";
ctx.moveTo(200, 20);
ctx.lineTo(200, 130);
ctx.lineTo(50, 20);
ctx.stroke();

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

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

// Control points
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(200, 130, 5, 0, 2 * Math.PI); // Control point one
ctx.arc(50, 20, 5, 0, 2 * Math.PI); // Control point two
ctx.fill();

结果

在此示例中,由arcTo() 创建的路径是粗黑线。切线为灰色,控制点为红色,起点为蓝色。

创建圆角

此示例使用arcTo() 创建圆角。这是该方法最常见的用途之一。

HTML

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

JavaScript

弧线从moveTo() 指定的点 (230, 20) 开始。它被塑造成以 (90, 130) 和 (20, 20) 为控制点,半径为 50。lineTo() 方法用直线将弧线连接到 (20, 20)。请注意,弧线的第二个控制点和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() 指定的坐标。这是因为半径太大,圆弧无法完全位于起点下方。

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();

结果

Constructing an arcTo() path

该演示显示了用于确定arcTo() 渲染路径的半无限直线以及中心为C、在T1T2 处与直线相切的圆。

请注意,当所有点共线时,arcTo 会创建一条从P0P1 的直线。此外,如果P0P1 的坐标相同,arcTo 不会绘制任何内容。

除了可以通过滑块设置圆弧半径外,还可以通过按住鼠标左键拖动来移动初始点P0 和控制点P1P2。还可以编辑数值,并使用箭头键更改处于焦点下的下划线元素。

arcTo() 绘制添加动画

对于此示例,您可以尝试调整圆弧半径,以查看路径如何变化。路径从起点p0 开始,使用具有控制点p1p2arcTo() 以及半径在 0 到滑块选择的最大半径之间变化。然后,调用lineTo() 将路径完成到p2

HTML

html
<div>
  <label for="radius">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");
let radius = control.value; // match with init control value
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 };

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);

结果

规范

规范
HTML
# dom-context-2d-arcto-dev

浏览器兼容性

另见