canvas画平滑贝塞尔线的关键是控制点需满足c¹连续(切线一致),而非简单分段;常用对称伸缩法或catmull-rom转贝塞尔法生成控制点,并配合正确绘制流程与动态优化策略。

Canvas 画平滑贝塞尔线,关键不在“多画几段”,而在于控制点怎么算——它得让相邻曲线段在连接处切线一致,否则再圆润也会露棱角。真正平滑的线条,是视觉上无折痕、数学上 C¹ 连续(一阶导数连续)的路径。
为什么普通连线不平滑?
用 lineTo() 直接连点,每段都是直线,拐点处角度突变,人眼立刻感知为“生硬”。贝塞尔本身不会自动保证段间光滑;若控制点随意设,两段曲线在锚点处可能方向打架,出现明显“扭结”或“回钩”。
核心:让控制点服从切线连续原则
对一组锚点 P₀, P₁, P₂, ..., Pₙ,要让整条曲线 C¹ 连续,每个内部锚点 Pᵢ(1 ≤ i ≤ n−1) 处左右两段曲线的切线方向必须一致。常用且实用的控制点生成策略如下:
-
对称伸缩法(推荐初学者):以
Pᵢ为基准,左右控制点分别设在Pᵢ₋₁→Pᵢ和Pᵢ→Pᵢ₊₁方向的延伸线上,长度取相邻线段长度的 1/3~1/2。例如:
左控制点 C₁ᵢ = Pᵢ − 0.35 × (Pᵢ − Pᵢ₋₁)
右控制点 C₂ᵢ = Pᵢ + 0.35 × (Pᵢ₊₁ − Pᵢ) -
Catmull-Rom 转贝塞尔(适合轨迹拟合):直接将 Catmull-Rom 样条的切线公式转为贝塞尔控制点:
C₁ᵢ = Pᵢ + (Pᵢ₊₁ − Pᵢ₋₁) / 6
C₂ᵢ = Pᵢ₊₁ − (Pᵢ₊₂ − Pᵢ) / 6
该方法天然保形,对鼠标轨迹、手写笔迹效果极佳 - 首尾特殊处理:P₀ 和 Pₙ 只有一侧邻点,可设外延控制点——比如用 P₁−P₀ 向量延长,或直接令 C₁₀ = P₀,C₂ₙ₋₁ = Pₙ,确保起点/终点精确落点
Canvas 绘制时的关键配合
算法算出控制点后,绘制本身要避免常见断点:
- 用
moveTo(P₀.x, P₀.y)显式起笔,之后每段调用bezierCurveTo(C₁ᵢ.x, C₁ᵢ.y, C₂ᵢ.x, C₂ᵢ.y, Pᵢ₊₁.x, Pᵢ₊₁.y) - 不要在每段之间调用
beginPath()或重复moveTo(),否则路径中断,无法形成连续描边 - 开启圆角连接:
ctx.lineJoin = 'round',能掩盖微小数值误差导致的接缝
动态数据下的轻量平滑更新
当数据流式追加(如实时监控曲线),无需重算全部控制点:
- 只维护最近 N 个锚点(如 50 点滑动窗口),每次新增点后,仅重新计算最后 2~3 段的控制点
- 局部重绘:用
clearRect()清除旧末端区域,再用新控制点重绘最后 1~2 段,避免全屏闪烁 - 控制点系数(如上面的 0.35)可随数据密度动态调整:点密则减小系数(更贴线),点疏则增大(更舒展)











