在Canvas中如何实现正弦运动(缓动效果)

作者:袖梨 2026-07-15

本文介绍如何利用正弦函数为 canvas 中的图形创建平滑的往复运动——物体在两端减速停止、在中心加速,模拟自然物理节奏。核心是将时间映射到 [0, 1] 区间的正弦缓动函数,并线性映射到目标位置范围。

本文介绍如何利用正弦函数为 canvas 中的图形创建平滑的往复运动——物体在两端减速停止、在中心加速,模拟自然物理节奏。核心是将时间映射到 [0, 1] 区间的正弦缓动函数,并线性映射到目标位置范围。

要实现“正弦运动”(即具有自然加减速特性的往复运动),关键不在于对坐标做 Math.sin(x) 这类简单变换,而应将时间作为输入,通过正弦缓动函数生成 [0, 1] 范围内的平滑插值系数,再将其线性映射到实际像素区间。这样可确保运动轨迹连续、可预测,且完全避免手动方向翻转与边界检测带来的跳跃或误差。

✅ 正确的正弦缓动函数设计

推荐使用如下标准化缓动函数:

const easeSin = (t) => Math.sin(t - Math.PI / 2) * 0.5 + 0.5;
  • t - Math.PI / 2:将正弦波相位左移,使 t = 0 时输出为 -1,从而让整个周期从谷底开始;
  • * 0.5 + 0.5:将原始范围 [-1, 1] 映射为 [0, 1],便于后续线性插值;
  • 输入 t 应随时间均匀增长(如 time * 0.001),保证周期稳定。

✅ 完整可运行示例

<canvas width="400" height="200"></canvas><style> canvas { border: 2px solid #333; } </style><script>  const canvas = document.querySelector('canvas');  const ctx = canvas.getContext('2d');  const rad = 20; // 圆半径  const y = 100;  // 垂直位置固定  const leftEdge = rad;           // 左边界(防止裁剪)  const rightEdge = canvas.width - rad; // 右边界  const easeSin = (t) => Math.sin(t - Math.PI / 2) * 0.5 + 0.5;  function motion(timeStamp) {    // 将时间缩放为合适频率(越小越慢,越大越快)    const t = timeStamp * 0.001;    const ease = easeSin(t);    // 线性插值:[0,1] → [leftEdge, rightEdge]    const x = leftEdge + ease * (rightEdge - leftEdge);    // 清屏 & 绘制    ctx.clearRect(0, 0, canvas.width, canvas.height);    ctx.beginPath();    ctx.fillStyle = 'steelblue';    ctx.arc(x, y, rad, 0, Math.PI * 2);    ctx.fill();    ctx.closePath();    requestAnimationFrame(motion);  }  requestAnimationFrame(motion);</script>

⚠️ 注意事项与优化建议

  • 避免使用独立计数器(如 q++):手动维护时间变量易导致帧率依赖、不同设备表现不一致;优先采用 requestAnimationFrame 的 timeStamp 参数,它提供高精度单调递增时间戳。
  • 边界处理更可靠:直接通过 leftEdge 和 rightEdge 控制有效运动区间,无需 if 判断方向反转,彻底消除因浮点误差或帧跳变引发的抖动或越界。
  • 可扩展性:该模式天然支持多维运动(如 x 和 y 同时应用不同相位/频率的 easeSin)、组合缓动(如 easeSin(t) * easeSin(t * 1.3)),也易于接入动画库或 Web Animations API。
  • 性能提示:clearRect 比 fillRect(0,0,w,h) 更高效;arc() 后调用 closePath() 非必需(fill() 会自动闭合),但保留无害。

掌握这种基于时间的正弦插值思想,不仅能实现丝滑的往返运动,更是构建复杂交互动画(如呼吸效果、摆动、弹性反馈)的基础范式。

相关文章

精彩推荐