← 几何 · 平面里的那些「判定」 / 彻底理解 Canvas / SVG 的圆弧(同一段弧,两套参数) 待审核 5 / 8
concept · elliptical arc

彻底理解 Canvas / SVG 的圆弧(同一段弧,两套参数)

一条弧线,本质是椭圆的一段。要唯一确定它,至少要给:圆心 (cx, cy)、长短轴半径 rx, ry、整椭圆的旋转角 rotation、起始角 startAngle、终止角 endAngle。这正是 Canvasctx.ellipse() 喂进去的参数——圆心已知,算哪个点都只是一次三角函数,所以好算SVG<path> 弧线命令 A 却反过来:只给起点、终点与半径、朝向,圆心要解出来;而过两点、半径朝向已定的椭圆一般有两个,各被两点切成两段弧——一共 4 段,得靠两个标志位 large-arc-flagsweep-flag 从中选 1 段。这一页把两套参数摆在一起,拖一下就看清它们其实等价

上方画布换两种模式:SVG 端点参数模式里拖起点终点,调 rx / ry / rotation,点 large-arc-flagsweep-flag 两个开关从 4 段弧里选一段;Canvas 中心参数模式里拖圆心,调起止角与 anticlockwise。两种模式下方都会实时换算出另一套参数——端点模式报出解出来的圆心与起止角,中心模式报出对应的 A 命令与两个 flag。

1 · Canvas:给圆心和起止角,anticlockwise 选哪一段

Canvas 有两个相关 API:arc(x, y, r, startAngle, endAngle, anticlockwise)正圆弧(没有 rx/ry 之分,也没有旋转角);ellipse(x, y, rx, ry, rotation, startAngle, endAngle, anticlockwise)椭圆弧。它们走的是中心参数——圆心、半径、朝向、起止角全部直接给出。

最后那个布尔 anticlockwise 的意义是:当起止角定下来后,从起点到终点其实有两段弧(顺时针走一段、逆时针走另一段)。anticlockwise = false 取顺时针那段,true 取逆时针那段。注意 Canvas 角度以正 x 轴为 0、顺屏幕方向(y 向下)递增——这页画布同理。中心参数下,弧上任意一点都是 (cx+rxcosθ,cy+rysinθ)(cx + rx\cdot \cos θ, cy + ry\cdot \sin θ) 再绕圆心旋转 rotation,圆心已知,一次代入就得到,这就是它「好算」的原因。

2 · SVG:给两个端点,large-arc-flagsweep-flag 各管一刀

SVG 的 <path>A rx ry x-axis-rotation large-arc-flag sweep-flag x y 画弧:起点取自当前点,命令尾部再给终点 (x, y)。相比 Canvas 给圆心 + 起止角,这种「给两端」的设计在画路径时更顺手,但更难算

给定半径、轴旋转、起点、终点后,过这两点的椭圆恰好有两个(互为镜像),它们被两点切成四段弧。两个 flag 各切一刀:其一 large-arc-flag——取大弧 (1) 还是小弧 (0);其二 sweep-flag——取正角方向(1,顺屏幕 / 顺时针) 还是反向 (0)。两刀下去,4 段里唯一确定 1 段。下面这张图把同一对端点、同一组半径下的四种组合并排画出——当前画布选中的那格会高亮(端点参数模式下与上方两个开关联动):

SVG 之所以比 Canvas 难算,正在于此:起止角可能不在 0..2π 这段里,要先解出圆心、再判断大弧 / 小弧、正向 / 反向。Canvas 把圆心和起止角直接交给你,省掉了这一整套反解。

3 · 两套参数其实等价:换算就是这一套公式

既然描述的是同一段弧,端点参数与中心参数必然可以互转。SVG 规范的 implementation notes(F.6.5)给了端点 → 中心 的完整步骤,本页画布的换算读数就是照它算的:

给起点 (x1,y1)、终点 (x2,y2)rx ry、旋转 ϕ\phi、两个 flag:其一 把弦中点搬到原点、转进「椭圆未旋转」的坐标系,得 (x1′, y1′);其二 解出该坐标系里的圆心 (cx′, cy′)——那个 ±\pm 号正是由 large-arc-flag 是否等于 sweep-flag 决定的(选两个候选椭圆中的哪一个);其三 转回原坐标系得真正圆心 (cx, cy),再用向量夹角求出起始角 θ1θ_1 与扫掠角 Δθ,最后按 sweep-flagΔθ 的符号归一到正 / 反一侧。

反过来 中心 → 端点 简单得多:起点 = θ1θ_1 处的椭圆点、终点 = θ1+Δθθ_1+Δθ 处的椭圆点;large-arc-flag = |Δθ| > 180°sweep-flag = Δθ > 0。把 Canvas 的 ellipse() 参数翻成 SVG 的 A 命令就靠这几行——这也是 ECharts 这类同时支持 Canvas 与 SVG 双引擎渲染的可视化库,能用一份图形描述喂两套 API 的关键。

4 · 退化与边界情形

半径太小撑不开两点。rx / ry 不足以让椭圆同时过起点和终点,SVG 规范要求把两个半径等比放大到刚好容纳(公式里的 Λ > 1 分支)。此时两个候选椭圆退化成同一个,四段弧塌成两段——把上方 rx / ry 调到很小就能看到读数里标出「半径已放大」。半径为 0:弧退化成直线段(SVG 直接按直线绘制)。起点 = 终点:整段弧不绘制(没有方向可言)。这些都是 Canvas 那套中心参数里不会遇到、只有端点参数才要特判的边界。

5 · 它用在哪里

双引擎可视化(ECharts、ZRender):初始化时选 Canvas 或 SVG 渲染,底层图形要把同一段弧分别落到 ctx.ellipse()<path A> 上,靠的正是上面这套互转。饼图 / 仪表盘 / 环形进度:扇区边界都是椭圆弧,常以中心参数算、以 SVG 路径输出。路径动画与描边:<path> 的弧线段配合 stroke-dasharray / offset-path 做沿弧运动。字体与矢量图形:虽然字形轮廓多用贝塞尔,但图标、地图投影里的弧线仍大量用到 A 命令。理解了「同一段弧、两套参数、可以互转」,这些场景就都落到同一套数学上。

6 · 🔗 相关链接

  • 彻底理解 Canvas/SVG 圆弧算法 · 羡辙 / 知乎——本页出处:从「一条弧需要哪些参数」讲起,对比 Canvas 的 arc / ellipse 与 SVG 的 A 命令,讲清 anticlockwise、large-arc-flag、sweep-flag 各自选的是哪段弧,以及这在 ECharts 双引擎渲染里为何关键。
  • SVG arc implementation notes (F.6) · W3C — 规范侧权威:端点参数 ↔ 中心参数的完整换算公式 (F.6.5)、半径修正 (F.6.6)、退化情形 (F.6.2)——本页画布的换算读数与半径放大逻辑全部照它实现。
  • SVG 路径:椭圆弧命令 A · MDN — A rx ry x-axis-rotation large-arc-flag sweep-flag x y 各参数的速查与图解,本页 path 字符串就照它生成。
  • CanvasRenderingContext2D.ellipse() · MDN — Canvas 中心参数侧:ellipse(x, y, rx, ry, rotation, startAngle, endAngle, anticlockwise) 的语义与角度方向(y 向下、顺屏幕递增)。

:::