You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JavaScript多Particle实例动画异常:仅首个实例执行动画的修复方案咨询

修复多Particle实例动画冲突的方案

你的判断方向没错,但问题核心不是requestAnimationFrame(this.animate.bind(this))本身,而是每个粒子都独立启动了自己的动画循环,并且在各自的循环里频繁清屏,导致后面粒子的绘制覆盖了前面的内容,最终看起来只有一个粒子在动。

问题根源拆解

  • 重复清屏干扰:每个Particle的animate方法里都执行了ctx.clearRect(0,0, canvas.width, canvas.height),每帧会被清屏多次,最后只有最后执行绘制的粒子能保留显示。
  • 多动画循环冲突:120个粒子就启动了120个独立的requestAnimationFrame循环,这些异步循环互相干扰,导致绘制逻辑完全混乱。

修复思路:单一全局动画循环

正确的做法是只维护一个全局的动画循环,在这个循环里统一处理所有粒子的更新和绘制,避免重复操作和冲突。

修改后的完整代码

//全局变量
let particles = [];
let numberParticles = 120;
let canvas;
let ctx;
let title;
let bound;

window.onload = function () {
    this.init();
    // 启动全局动画循环,只需要调用一次
    requestAnimationFrame(animate);
}

function init(){
    canvas = document.getElementById("c");
    ctx = canvas.getContext("2d");
    title = document.getElementById("title");
    const mesaureTitle = title.getBoundingClientRect();
    bound = {
        x: mesaureTitle.x,
        y: mesaureTitle.y,
        width: mesaureTitle.width,
        height: mesaureTitle.height,
    };
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    for(let i = 0; i < numberParticles; i++){
        let x = Math.floor(Math.random() * window.innerWidth);
        let y = Math.floor(Math.random() * window.innerHeight);
        let size = Math.floor(Math.random() * 25) + 3;
        let weight = Math.floor(Math.random() * 11) + 2;
        particles.push(new Particle(x, y, size, weight));
    }
}

// 全局动画循环函数
function animate(timeStamp) {
    // 每帧只清屏一次
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 遍历所有粒子,统一执行更新和绘制
    particles.forEach(particle => {
        particle.update();
        particle.draw();
    });
    
    // 继续下一轮动画
    requestAnimationFrame(animate);
}

class Particle {
    constructor (x,y,size, weight) {
        this.x = x;
        this.y = y;
        this.size = size;
        this.directionX = 0.15332;
        this.resetWeight = weight;
        this.weight = weight;
    }
    update(){
        this.weight += 0.02;
        this.y = this.y + this.weight;
        this.x += this.directionX;
        //检测与文本区域的碰撞
        if (this.x < bound.x + bound.width && this.x + this.size > bound.x && this.y < bound.y + bound.height && this.y + this.size > bound.y) {
            this.y -= 3;
            this.weight *= -0.3;
        }
    }
    draw(){
        if(this.y > canvas.height){
            this.y = 0 - this.size;
            this.weight = this.resetWeight;
            //生成随机起始点
            this.x = Math.floor(Math.random() * canvas.width);
        }
        ctx.fillStyle = "rgb(0, 180, 97)";
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
        ctx.closePath();
        ctx.fill();
    }
    // 移除原来的animate方法,交给全局循环统一处理
}

关键改动点

  1. 剥离粒子自身的动画逻辑:把animate方法从Particle类中移除,避免每个粒子启动独立的动画循环。
  2. 新增全局动画循环:这个函数负责每帧的清屏操作,然后遍历所有粒子执行updatedraw,最后只调用一次requestAnimationFrame维持循环。
  3. 简化窗口加载逻辑:不再遍历粒子调用各自的animate,只需要启动一次全局循环即可。
  4. 移除冗余时间控制requestAnimationFrame本身会以浏览器的刷新频率(通常60fps)执行,不需要粒子内部的lastTimeinterval等变量来控制间隔。

这样修改后,所有粒子会在同一个动画循环里有序更新和绘制,就不会出现只有一个粒子动的问题了。

内容的提问来源于stack exchange,提问作者Shiroe Akamure

火山引擎 最新活动