CanvasRenderingContext2D:arcTo() 方法

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

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

这个方法通常用于创建圆角。

备注: 当使用相对较大的半径时,可能会得到意外的结果:连接圆弧的直线将以必要的方向延申以符合指定的半径。

语法

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

参数

x1

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

y1

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

x2

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

y2

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

radius

圆弧的半径。必须为非负值。

使用说明

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

  • 如果 r 是负数,则会引发 IndexSizeError 异常
  • 如果 r 是 0,arcTo() 方法会表现得好像 P0P1P2 共线。
  • 如果所有点都共线,会从 P0P1 绘制一条直线,除非点 P0P1 是重合的(坐标相同),此时不会绘制任何内容。

可以查看下面的构建一条 arcTo() 路径示例所创建这些条件。

返回值

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

// 切线段
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); // 控制点一
ctx.arc(50, 20, 5, 0, 2 * Math.PI); // 控制点二
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();

结果

构建一条 arcTo() 路径

此演示展示了射线(semi-infinite line)和以 C 为圆心并在 T1T2 处与射线相切的圆弧被用于确定 arcTo() 方法的渲染路径。

需要注意的是,当所有点都在一条直线上时,arcTo() 方法会在 P0P1 之间创建一条直线。此外,如果 P0P1 具有相同的坐标,arcTo() 方法不会绘制任何内容。

除了可以通过滑块设置弧度半径外,初始点 P0 和控制点 P1P2 可以通过按住鼠标左键拖动来移动。数值也可以直接编辑,使用箭头键可以改变被聚焦的具有下划线标记的元素。

绘制 arcTo() 的动画

在这个示例中,你可以通过调整弧度半径来观察路径的变化。路径是从起始点 p0 开始使用 arcTo() 方法绘制的,控制点为 p1p2,弧度半径从 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; // 匹配初始控件值

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

// 绘制弧线
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

参见