如何在React应用中引入外部JavaScript粒子动画脚本
问题原因
- 违反React钩子使用规则:钩子必须在组件顶层作用域调用,不能放在JSX的
{}内部执行,会导致钩子执行逻辑异常。 - 脚本执行时机错误:你的粒子脚本加载后会立即执行
document.querySelector('.particles')查询容器,但此时React组件还未完成挂载,容器还没有插入到DOM中,查询结果为null,执行container.appendChild(canvas)直接抛出异常,脚本终止运行。 - 资源未清理:脚本运行后创建的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
相关产品推荐
相关产品推荐

