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
# dom-context-2d-arcto-dev

浏览器兼容性

Report problems with this compatibility data on GitHub
desktopmobile
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
arcTo

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support

参见