
本文介绍如何利用正弦函数(sin)在 html canvas 中实现平滑的往复运动:物体在边界处减速至停、再反向加速,形成自然的“呼吸式”缓动效果。核心是将时间映射为 [0, 1] 区间的正弦缓动值,并线性映射到目标位移范围。
本文介绍如何利用正弦函数(sin)在 html canvas 中实现平滑的往复运动:物体在边界处减速至停、再反向加速,形成自然的“呼吸式”缓动效果。核心是将时间映射为 [0, 1] 区间的正弦缓动值,并线性映射到目标位移范围。
要让一个圆点在 Canvas 上呈现真实的正弦运动——即速度随位置变化:在两端(极值点)瞬时静止,在中心区域速度最快——关键在于用正弦函数控制位置,而非直接修改速度或累加位移。你原始代码中 x += chang 是线性递增,配合 Math.sin(q) 仅用于坐标缩放,这并未真正实现物理意义上的正弦运动(即位移是时间的正弦函数),反而导致轨迹失真和逻辑混乱。
正确的做法是:将动画时间戳(requestAnimationFrame 提供的 time 参数)作为输入,经自定义正弦缓动函数转换为 [0, 1] 范围内的归一化进度值,再将其线性映射到实际像素区间。
以下是推荐实现:
// 正弦缓动函数:输入时间 t,输出 [0, 1] 的平滑周期性值
const easeSin = (t) => Math.sin(t - Math.PI / 2) * 0.5 + 0.5;
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const rad = 20; // 圆点半径
const y = 100; // 垂直位置固定
// 定义水平运动范围:左边界 = rad,右边界 = canvas.width - rad
// 确保圆始终完全可见
const leftBound = rad;
const rightBound = canvas.width - rad;
function motion(time) {
// 缓动值:0 → 1 → 0 → 1... 周期性变化(频率由 time * 0.001 控制)
const progress = easeSin(time * 0.001);
// 将 progress 映射到 [leftBound, rightBound] 区间
const x = leftBound + progress * (rightBound - leftBound);
// 清屏并绘制
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(x, y, rad, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
requestAnimationFrame(motion);
}
// 启动动画
requestAnimationFrame(motion);✅ 为什么这个方案更优?
- 物理准确:x(t) = a + b·sin(ωt + φ) 形式天然满足加速度连续、端点速度为零的特性;
- 无状态依赖:不维护 x、chang、q 等易出错的中间变量,避免累积误差与方向判断逻辑;
- 可扩展性强:轻松适配任意方向(Y轴/对角线)、多物体或叠加其他缓动(如 easeSinOut);
- 性能友好:单次计算,无条件分支与边界检查。
? 注意事项:
- time * 0.001 中的 0.001 是频率调节系数,数值越小运动越慢,越大越快;
- 若需严格等周期往返(如每 2 秒完成一次来回),建议改用 Date.now() 或自增 elapsedTime 变量,避免 requestAnimationFrame 时间戳因页面后台暂停而跳变;
- 实际项目中应添加 canvas.width 初始化(例如 <canvas width="600" height="200"></canvas>),防止默认尺寸导致渲染异常。
通过将时间→正弦→位置的映射关系显式建模,你就能获得真正符合运动学直觉的平滑动画——这才是 Canvas 正弦运动的正确打开方式。


















