如何在切换React路由时停止react-p5-wrapper后台运行draw函数
解决方案
你遇到的问题核心有两点:
sketch函数、音频变量等都定义在组件外部,属于模块全局作用域,组件卸载时这些资源不会被自动回收,p5的绘制循环会持续持有引用一直运行- 没有在组件卸载时手动销毁p5实例、停止绘制循环
按照以下步骤修改game.js即可:
- 把所有全局作用域的变量和
sketch函数都挪到CatchingGame组件内部 - 利用
react-p5-wrapper的实例引用,在组件卸载时调用p5自带的remove()方法销毁实例,同时清理音频资源 - 给
useEffect补充空依赖数组,避免重复创建音频实例
修改后完整代码
import { useEffect, useRef } from "react"; import { ReactP5Wrapper, P5Instance } from "react-p5-wrapper"; const CatchingGame = () => { const p5Ref = useRef<P5Instance | null>(null); useEffect(() => { const pointSound = new Audio("/game/points.wav"); const endSound = new Audio("/game/end.wav"); pointSound.volume = 0.3; let playEndSound = false; const sketch = (p: P5Instance) => { p5Ref.current = p; const MAX_SPEED = 15; const pickDirections = () => { return ((Math.floor(Math.random() * 3) % 2 === 0 ? 1 : -1) * (Math.floor(Math.random() * 2) + 1)); }; const randomXPos = () => { return p.random(30, p.width - 30); }; const ramdomImgIndex = () => { return Math.floor(Math.random() * imgs.length); }; const reset = () => { score = 0; speed = 2; falls = [ { y: -70, x: randomXPos(), rotation: 0, direction: pickDirections(), imgIndex: ramdomImgIndex() } ]; }; const rotate_n_draw_image = (image,img_x,img_y,img_width,img_height,img_angle) => { p.imageMode(p.CENTER); p.translate(img_x + img_width / 2, img_y + img_width / 2); p.rotate((Math.PI / 180) * img_angle); p.image(image, 0, 0, img_width, img_height); p.rotate((-Math.PI / 180) * img_angle); p.translate(-(img_x + img_width / 2), -(img_y + img_width / 2)); p.imageMode(p.CORNER); }; // Images let imgs = [], basket = { img: null, width: 150, height: 150 }, imgSize = 50; let screen = 0, falls = [ { y: -70, x: randomXPos(), rotation: 0, direction: pickDirections(), imgIndex: ramdomImgIndex() } ], score = 0, speed = 2; const startScreen = () => { p.background(205, 165, 142); p.fill(255); p.textAlign(p.CENTER); p.text("WELCOME TO MY CATCHING GAME", p.width / 2, p.height / 2); p.text("click to start", p.width / 2, p.height / 2 + 20); reset(); }; const gameOn = () => { p.background(219, 178, 157); p.textAlign(p.LEFT); p.text("score = " + score, 30, 20); falls = falls.map(({ x, y, rotation, direction, imgIndex }) => { // rotate while dropping rotation += direction; // dropping y += speed; return { x, y, rotation, direction, imgIndex }; }); falls.forEach(({ x, y, rotation, imgIndex }, i) => { // when is lower than the border line if (y > p.height) { screen = 2; playEndSound = true; } // when reaching the border line and is within the range if ( y > p.height - 50 && x > p.mouseX - basket.width / 2 && x < p.mouseX + basket.width / 2 ) { // Play Sound pointSound.currentTime = 0; pointSound.play(); // Increase Score score += 10; // Increase Speed if (speed < MAX_SPEED) { speed += 0.1; speed = parseFloat(speed.toFixed(2)); } // Whether add new item into array or not if (i === falls.length - 1 && falls.length < 3) { falls.push({ x: randomXPos(), y: -70 - p.height / 3, rotation: 0, direction: pickDirections(), imgIndex: ramdomImgIndex() }); } falls[i].y = -70; falls[i].x = randomXPos(); falls[i].imgIndex = ramdomImgIndex(); } rotate_n_draw_image(imgs[imgIndex], x, y, imgSize, imgSize, rotation); }); p.imageMode(p.CENTER); p.image( basket.img, p.mouseX, p.height - basket.height / 2, basket.width, basket.height ); }; const endScreen = () => { if (playEndSound) { endSound.play(); playEndSound = false; } p.background(205, 165, 142); p.textAlign(p.CENTER); p.text("GAME OVER", p.width / 2, p.height / 2); p.text("SCORE = " + score, p.width / 2, p.height / 2 + 20); p.text("click to play again", p.width / 2, p.height / 2 + 60); }; p.preload = () => { // Load Images imgs[0] = p.loadImage("https://dummyimage.com/400x400"); imgs[1] = p.loadImage("https://dummyimage.com/400x400"); imgs[2] = p.loadImage("https://dummyimage.com/401x401"); basket.img = p.loadImage("https://dummyimage.com/500x500"); }; p.setup = () => { p.createCanvas( window.innerWidth > 400 ? 400 : window.innerWidth, window.innerHeight > 500 ? 500 : window.innerHeight ); }; p.draw = () => { console.log("running draw"); switch (screen) { case 0: startScreen(); break; case 1: gameOn(); break; case 2: endScreen(); break; default: } }; p.mousePressed = () => { if (screen === 0) { screen = 1; } else if (screen === 2) { screen = 0; } }; }; return () => { // 卸载时销毁p5实例 if (p5Ref.current) { p5Ref.current.remove(); p5Ref.current = null; } // 清理音频资源 pointSound.pause(); pointSound.src = ""; endSound.pause(); endSound.src = ""; }; }, []); return ( <div className="mx-auto flex justify-center items-center"> <ReactP5Wrapper sketch={sketch} /> </div> ); }; export default CatchingGame;
修改后切换路由时组件卸载会自动触发清理逻辑,销毁p5实例、停止draw循环,就不会再出现后台运行的问题了。
内容的提问来源于stack exchange,提问作者XH栩恒
相关产品推荐
相关产品推荐

