canvas曲线动画核心是让物体沿参数化路径平滑运动:用requestanimationframe驱动,结合贝塞尔公式计算t时刻坐标,并通过缓动函数优化启停体验。

Canvas 动画实现曲线平滑运动,核心不是“画曲线”,而是让物体“走”在曲线上——靠 requestAnimationFrame 驱动时间步进,配合路径参数化与插值计算,逐帧更新位置。生硬的累加、固定步长或 setInterval 都会导致卡顿、忽快忽慢或掉帧。
用 requestAnimationFrame 替代定时器驱动
requestAnimationFrame 自动对齐屏幕刷新率(通常 60Hz),避免 setTimeout 的时机漂移和后台掉帧问题。它还会在标签页不可见时暂停,节省资源。
- 每帧回调接收高精度时间戳(
timestamp),不是Date.now() - 必须在回调末尾主动调用
requestAnimationFrame(animate),否则只执行一帧 - 动画开始前记录起始时间:
let startTime = null;首次回调中赋值:if (!startTime) startTime = timestamp
把曲线变成可计算的点序列(路径参数化)
Canvas 的 quadraticCurveTo 或 bezierCurveTo 只负责绘制,不提供“t=0.3 时坐标在哪”的查询能力。要让小球沿曲线移动,你得自己算出每个进度对应的 (x, y)。
- 二阶贝塞尔(一个控制点):用公式
p(t) = (1−t)²·P₀ + 2(1−t)t·P₁ + t²·P₂,或用 De Casteljau 几何切分法逐步插值 - 三阶贝塞尔(两个控制点):公式为
p(t) = (1−t)³·P₀ + 3(1−t)²t·P₁ + 3(1−t)t²·P₂ + t³·P₃ - 关键细节:t ∈ [0, 1] 表示归一化进度;但线性 t 并不等于匀速运动——曲率大的地方会“拖慢”。若需视觉匀速,应先做弧长归一化(对复杂路径可用采样+累加近似)
加缓动让启停更自然
直接用线性 progress(即 t = (now − startTime) / duration)会让运动生硬,尤其在起点和终点。换成缓动函数,能模拟惯性与柔顺过渡。
- 常用 cubic ease-in-out:
function easeInOutCubic(t) { return t - 把原始 progress 输入该函数,再传给贝塞尔公式,得到的坐标变化就具备加减速效果
- 避免在动画帧中做大量计算——缓动函数本身应轻量,路径预计算或离屏缓存可进一步优化性能
渲染时注意亚像素与抗锯齿
即使逻辑完美,渲染层处理不当也会破坏平滑感:
- 坐标保留小数(如
x: 127.38),别Math.round()强制取整;Canvas 会自动做亚像素混合 - 确保
ctx.imageSmoothingEnabled = true(默认开启,但缩放图片时需显式确认) - 清空画布用
clearRect(0, 0, canvas.width, canvas.height),避免残留残影;重绘顺序:背景 → 路径(可选)→ 移动物体











