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

如何在切换React路由时停止react-p5-wrapper后台运行draw函数

解决方案

你遇到的问题核心有两点:

  • sketch函数、音频变量等都定义在组件外部,属于模块全局作用域,组件卸载时这些资源不会被自动回收,p5的绘制循环会持续持有引用一直运行
  • 没有在组件卸载时手动销毁p5实例、停止绘制循环

按照以下步骤修改game.js即可:

  1. 把所有全局作用域的变量和sketch函数都挪到CatchingGame组件内部
  2. 利用react-p5-wrapper的实例引用,在组件卸载时调用p5自带的remove()方法销毁实例,同时清理音频资源
  3. 给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栩恒

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 10:36:04