粒子动画坐标异常排查:Math.atan2等函数逻辑疑问
粒子动画坐标异常与角度计算逻辑疑问
问题描述
粒子动画坐标显示异常,尽管距离和力的计算看似正确。同时需要理解update()函数中Math.atan2(dy, dx)、Math.cos()和Math.sin()的工作原理:
已知参数:
- 鼠标坐标:
x:740、y:260 - 粒子初始坐标:
x:673、y:226 - 粒子初始速度:
speedX:0.5、speedY:0.5
点击鼠标后,粒子向相反方向运动,speedX变为-0.5、speedY变为-0.5,最终到达约x:536、y:204的位置。
手动计算过程
粒子与鼠标的偏移量:
dx = 673 - 740 = -67dy = 226 - 260 = -34- 距离:
≈76.655(小于鼠标半径100,会受到斥力) - 斥力大小:
≈100 / 76.655 = 1.303 - 角度:
Math.atan2(-34, -67) ≈ -2.701弧度 - 水平斥力分量:
pushX ≈ Math.cos(-2.701) * 1.303 ≈ -1.094 - 垂直斥力分量:
pushY ≈ Math.sin(-2.701) * 1.303 ≈ -0.465
按此计算粒子应到达约x:673 - 1.094 ≈ 172.406、y:226 - 0.465 ≈ 225.035,但实际结果与计算不符,请求解释原因。
代码实现
class Point { constructor(effect) { this.effect = effect; this.radius = 15; this.x = this.radius + Math.random() * (this.effect.width - this.radius * 2); this.y = this.radius + Math.random() * (this.effect.height - this.radius * 2); this.speedX = 0.5; this.speedY = 0.5; this.pushX = 0; this.pushY = 0; this.friction = 0.95; } draw(context) { context.beginPath(); context.arc(this.x, this.y, this.radius, 0, Math.PI * 2); context.fill(); } update() { if(this.effect.mouse.pressed) { const dx = this.x - this.effect.mouse.x; const dy = this.y - this.effect.mouse.y; const distance = Math.sqrt(dx * dx + dy * dy); const force = this.effect.mouse.radius / distance; if(distance < this.effect.mouse.radius) { const angle = Math.atan2(dy, dx); this.pushX += Math.cos(angle) * force; this.pushY += Math.sin(angle) * force; } } this.x += (this.pushX *= this.friction) + this.speedX; this.y += (this.pushY *= this.friction) + this.speedY; if(this.x < this.radius) { this.x = this.radius; this.speedX *= -1; } else if(this.x > this.effect.width - this.radius) { this.x = this.effect.width - this.radius; this.speedX *= -1; } if(this.y < this.radius) { this.y = this.radius; this.speedY *= -1; } else if(this.y > this.effect.height - this.radius) { this.y = this.effect.height - this.radius; this.speedY *= -1; } } } class Effect { constructor(canvas, context) { this.canvas = canvas; this.width = canvas.width; this.height = canvas.height; this.particles = []; this.numberOfParticles = 20; this.createParticles(); this.mouse = { x: 0, y: 0, radius: 100, pressed: false, } window.addEventListener('mousemove', e => { this.mouse.x = e.x; this.mouse.y = e.y; }) window.addEventListener('mousedown', e => { this.mouse.pressed = true; this.mouse.x = e.x; this.mouse.y = e.y; console.log(this.mouse.x, this.mouse.y); }) window.addEventListener('mouseup', e => { this.mouse.pressed = false; }) } createParticles() { for(let i = 0; i < this.numberOfParticles; i++) { this.particles.push(new Point(this)); } } handleParticles(context) { this.particles.forEach(particle => { particle.draw(context); particle.update(); }) } } const effect = new Effect(canvas, ctx); function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); effect.handleParticles(ctx); requestAnimationFrame(animate); } animate();
问题原因与逻辑详解
1. 一次性计算 vs 帧迭代更新
你手动计算时错误地将pushX/pushY当成一次性位移量直接加到初始坐标,但代码中update()是每帧(约60次/秒)执行的动态过程:
- 每帧都会根据当前粒子与鼠标的位置重新计算斥力,累加到
pushX/pushY pushX *= this.friction会让斥力随时间逐渐衰减- 粒子坐标每帧累加
(pushX*摩擦系数) + 初始速度,是持续的动态变化,而非一步到位的位移
2. 角度与力的方向逻辑
Math.atan2(dy, dx)返回的是粒子相对于鼠标的位置向量((dx, dy) = (粒子x-鼠标x, 粒子y-鼠标y))与x轴正方向的夹角:
- 案例中
dx=-67、dy=-34,向量指向第三象限,角度≈-2.701弧度(等价于2π-2.701≈3.582弧度) Math.cos(angle)和Math.sin(angle)得到该方向的单位向量,乘以斥力后,pushX/pushY会给粒子一个远离鼠标的加速度(沿粒子相对于鼠标的位置方向运动,自然远离鼠标)
3. 边界碰撞的影响
代码中当粒子触碰画布边界时,会反转speedX/speedY:
你看到的speedX变为-0.5、speedY变为-0.5,是粒子运动中触发了边界碰撞逻辑,改变了初始速度方向,这是实际位置与手动计算不符的关键因素之一
内容的提问来源于stack exchange,提问作者digitArena
相关产品推荐
相关产品推荐

