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

如何在React应用中引入外部JavaScript粒子动画脚本

问题原因

  1. 违反React钩子使用规则:钩子必须在组件顶层作用域调用,不能放在JSX的{}内部执行,会导致钩子执行逻辑异常。
  2. 脚本执行时机错误:你的粒子脚本加载后会立即执行document.querySelector('.particles')查询容器,但此时React组件还未完成挂载,容器还没有插入到DOM中,查询结果为null,执行container.appendChild(canvas)直接抛出异常,脚本终止运行。
  3. 资源未清理:脚本运行后创建的requestAnimationFrame循环、resize事件监听在组件卸载时不会被销毁,会造成内存泄漏,多次加载还会重复创建动画实例。

解决办法

推荐直接将粒子动画改造为React组件,完全适配React生命周期,避免上述所有问题,代码如下:

import { useEffect, useRef } from 'react';

const Particles = () => {
  const containerRef = useRef(null);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    canvas.style.position = 'absolute';
    canvas.style.width = '100%';
    canvas.style.height = '100%';
    canvas.style.top = 0;
    canvas.style.left = 0;
    canvas.style.pointerEvents = 'none'; // 避免挡住页面其他元素的点击事件

    container.appendChild(canvas);

    let particleArray = [];
    let animationId = null;

    class Particle {
      constructor(x, y, dirX, dirY, size, color) {
        this.x = x;
        this.y = y;
        this.dirX = dirX;
        this.dirY = dirY;
        this.size = size;
        this.color = color;
        this.scale = .1;
        this.counter = 0;
      }
      draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
        ctx.shadowColor = this.color;
        ctx.shadowBlur = 10;
        ctx.fillStyle = this.color;
        ctx.fill();
      }
      update() {
        if (this.x + this.size > canvas.width || this.x - this.size < 0) {
          this.dirX = -this.dirX;
        }
        if (this.y + this.size > canvas.height || this.y - this.size < 0) {
          this.dirY = -this.dirY;
        }
        this.size += this.scale;
        this.counter += .1;
        if (this.counter > 3) {
          this.scale = -this.scale;
          this.counter = 0;
        }
        this.x += this.dirX;
        this.y += this.dirY;
        this.draw();
      }
    }

    function init() {
      particleArray = [];
      for (let i = 0; i < 25; i++) {
        let size = Math.random() * 3;
        let x = Math.random() * (window.innerWidth - size * 2);
        let y = Math.random() * (window.innerHeight - size * 2);
        let dirX = (Math.random() * .4) - .2;
        let dirY = (Math.random() * .4) - .2;
        let colors = ['#721240']
        let color = colors[Math.floor(Math.random() * colors.length)];
        particleArray.push(new Particle(x, y, dirX, dirY, size, color));
      }
    }

    function animate() {
      animationId = requestAnimationFrame(animate);
      ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
      for (let i = 0; i < particleArray.length; i++) {
        particleArray[i].update();
      }
    }

    const handleResize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      init();
    }

    init();
    animate();
    window.addEventListener('resize', handleResize);

    // 清理函数,组件卸载时销毁所有资源
    return () => {
      window.removeEventListener('resize', handleResize);
      cancelAnimationFrame(animationId);
      container.removeChild(canvas);
    };
  }, []);

  return <div ref={containerRef} className="particles" style={{ position: 'relative', width: '100%', height: '100vh' }} />;
};

export default Particles;

在App.js中直接引入使用即可:

import Particles from './Particles';

function App() {
  return (
    <div className="App">
      <Particles />
      {/* 其他页面内容 */}
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Dr.Dark.Crystals

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:24:02