为什么这个Canvas组件渲染两次并在结束时突然移动?



我一直在尝试在React中实现Roco C. Buljan的JS纺车,我完全被困在这个特定的点上。预期的行为是车轮旋转一次,停止后显示指向扇区。

下面是我的代码:
import React, { useEffect, useRef } from "react";
import "./SpinWheel.css";
function SpinWheel() {
const sectors = [
{ color: "#f82", label: "Stack" },
{ color: "#0bf", label: "10" },
{ color: "#fb0", label: "200" },
{ color: "#0fb", label: "50" },
{ color: "#b0f", label: "100" },
{ color: "#f0b", label: "5" },
];
// Generate random float in range min-max:
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const wheel = useRef(null);
const spin = useRef(null);
useEffect(() => {
const ctx = wheel.current.getContext("2d");
const elSpin = spin.current;
spinWheel(ctx, elSpin);
}, []);
function spinWheel(ctx, elSpin) {
const dia = ctx.canvas.width;
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;
const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
const angVelMin = 0.002; // Below that number will be treated as a stop
let angVelMax = 0; // Random ang.vel. to acceletare to
let angVel = 0; // Current angular velocity
let ang = 0; // Angle rotation in radians
let isSpinning = false;
let isAccelerating = false;
//* Get index of current sector */
const getIndex = () => Math.floor(tot - (ang / TAU) * tot) % tot;
//* Draw sectors and prizes texts to canvas */
const drawSector = (sector, i) => {
const ang = arc * i;
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(rad, rad);
ctx.arc(rad, rad, rad, ang, ang + arc);
ctx.lineTo(rad, rad);
ctx.fill();
// TEXT
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = "bold 30px sans-serif";
ctx.fillText(sector.label, rad - 10, 10);
//
ctx.restore();
};

//* CSS rotate CANVAS Element */
const rotate = () => {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
elSpin.textContent = !angVel ? sector.label : "SPIN";
elSpin.style.background = sector.color; 
};
const frame = () => {
if (!isSpinning) return;
if (angVel >= angVelMax) isAccelerating = false;
// Accelerate
if (isAccelerating) {
angVel ||= angVelMin; // Initial velocity kick
angVel *= 1.06; // Accelerate
}
// Decelerate
else {
isAccelerating = false;
angVel *= friction; // Decelerate by friction
// SPIN END:
if (angVel < angVelMin) {
isSpinning = false;
angVel = 0;
}
}
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate(); // CSS rotate!
};
const engine = () => {
frame();
requestAnimationFrame(engine);
};
elSpin.addEventListener("click", () => {
if (isSpinning) return;
isSpinning = true;
isAccelerating = true;
angVelMax = rand(0.25, 0.4);
});
// INIT!
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine!
}
return (
<div id="wheelOfFortune">
<canvas id="wheel" ref={wheel} width="300" height="300"></canvas>
<div id="spin" ref={spin}>
SPIN asd asd asd as dasd as dasd asd asd as d
</div>
</div>
);
}
export default SpinWheel;

这里是沙盒链接

问题是,如果我console.log的useEffect块,我可以看到它在开始时执行两次,这不是我想要的。我试着把一个空数组作为依赖数组,但它仍然不适合我。第二个问题是,按说轮子会停下来,然后再转一小圈,最终产生两个部门产出。再说一次,这不是我想要的。我只能猜测它被渲染了不止一次,但我不确定。知道我哪里做错了吗?

要使它在react上下文中工作,您需要做几件事:

  1. spinWheel方法应该返回控制车轮的启动/停止/清理方法。清理方法应该清理任何工件,如事件处理程序和诸如此类的东西。

    const startSpin = () => {
    if (isSpinning) return;
    isSpinning = true;
    isAccelerating = true;
    angVelMax = rand(0.25, 0.4);
    };
    const stopSpin = () => {
    isSpinning = false;
    isAccelerating = false;
    }
    elSpin.addEventListener("click", startSpin);
    const cleanup = () => {
    elSpin.removeEventListener("click", startSpin);
    };
    ...
    return { startSpin, stopSpin, cleanup };
    
  2. 正如评论中提到的,你需要确保你的效果有一个"清理"。停止车轮的功能(参见此链接)。这是必要的,以便安装→清理→安装顺序在该页所描述的开发中工作。

    useEffect(() => {
    const ctx = wheel.current.getContext("2d");
    const elSpin = spin.current;
    const { startSpin, stopSpin, cleanup } = spinWheel(ctx, elSpin);
    startSpin();
    return () => {
    stopSpin();
    cleanup();
    }
    }, []);
    

这是一个工作沙箱的链接:https://codesandbox.io/s/spin-wheel-forked-tb42zf?file=/src/components/SpinWheel/SpinWheel.js:546-826

最新更新