You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

粒子动画坐标异常排查: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 = -67
  • dy = 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:57:07