I am making a drawing app with React and Canvas, however if I set the isMouseDown
variable as useState instead of useRef, the canvas is not drawing and I cannot seem to find why. Here is the component:
`
function App() {
const [isMouseDown, setIsMouseDown] = useState(false);
const isMouseDownRef = useRef(false);
const canvasRef = useRef(null);
const ctx = useRef(null);
function triggerMouseDown() {
setIsMouseDown(true);
//isMouseDownRef.current = true;
}
function triggerMouseUp() {
setIsMouseDown(false);
//isMouseDownRef.current = false;
}
useEffect(() => {
if(canvasRef.current) {
ctx.current = canvasRef.current.getContext("2d");
canvasRef.current.width = 720;
canvasRef.current.height = 480;
canvasRef.current.addEventListener("mousemove", (e) => {
draw(
e.clientX - canvasRef.current.getBoundingClientRect().left,
e.offsetY - canvasRef.current.getBoundingClientRect().top
)
})
canvasRef.current.addEventListener("mousedown", triggerMouseDown)
window.addEventListener("mouseup", triggerMouseUp)
}
return () => {
canvasRef.current.removeEventListener("mousedown", triggerMouseDown);
window.removeEventListener("mouseup", triggerMouseUp)
}
}, [])
function draw(x, y) {
if(isMouseDown) {
ctx.current.beginPath();
ctx.current.fillStyle = "blue";
ctx.current.arc(x, y, 20, 0, 2 * Math.PI);
ctx.current.stroke();
}
}
return (
<div className="App">
<canvas id="canvas1" ref={canvasRef}></canvas>
<h1>{JSON.stringify(isMouseDown)}</h1>
</div>
);
}
`
I know it's not supposed to be a useState, beacause it would rerender too much, but I am intrested in why it's not working with useState in particular. Thank you!
CodePudding user response:
I made some changes inside your code:
const App = () => {
const [mouseData, setMouseData] = React.useState({ x: 0, y: 0 });
const canvasRef = React.useRef(null);
const ctx = React.useRef(null);
React.useEffect(() => {
if(canvasRef.current) {
ctx.current = canvasRef.current.getContext("2d");
canvasRef.current.width = 720;
canvasRef.current.height = 480;
}
}, [canvasRef])
function Draw(e) {
ctx.current.beginPath();
ctx.current.fillStyle = "red";
ctx.current.arc(mouseData.x, mouseData.y, 20, 0, 2 * Math.PI);
ctx.current.stroke();
}
const SetPos = (e) => {
setMouseData({
x: e.clientX,
y: e.clientY,
});
};
return (
<div className="App">
<canvas id="canvas1" ref={canvasRef}
onm ouseEnter={(e) => SetPos(e)}
onm ouseMove={(e) => SetPos(e)}
onm ouseDown={(e) => SetPos(e)}
onm ouseMove={(e) => Draw(e)}
></canvas>
<h1>{JSON.stringify(mouseData)}</h1>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
CodePudding user response:
Y_T has the right idea in this answer. There's no need for useEffect
, but if you do use one, it should use isMouseDown
in the capture group so that it triggers adding/removing the move listener at the appropriate time. clearRect
is optional. Something like:
useLayoutEffect(() => {
if (!mouseDown) {
return;
}
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const onm ouseMove = e => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(e.offsetX, e.offsetY, 20, 0, 2 * Math.PI);
ctx.stroke();
};
canvas.addEventListener("mousemove", onm ouseMove);
return () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.removeEventListener("mousemove", onm ouseMove);
};
}, [mouseDown]);
But since React gives onMouseMove
, we can just use that instead of managing it ourselves:
const App = () => {
const [mouseDown, setMouseDown] = React.useState(false);
const canvasRef = React.useRef(null);
const handleMouseMove = e => {
if (!mouseDown) {
return;
}
const rect = e.target.getBoundingClientRect();
const offsetX = e.clientX - rect.left;
const offsetY = e.clientY - rect.top;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
//ctx.clearRect(0, 0, canvas.width, canvas.height); // optional
ctx.beginPath();
ctx.arc(offsetX, offsetY, 20, 0, 2 * Math.PI);
ctx.stroke();
};
return (
<canvas
onm ouseDown={() => setMouseDown(true)}
onm ouseUp={() => setMouseDown(false)}
onm ouseMove={handleMouseMove}
ref={canvasRef}
width="720"
height="480"
></canvas>
);
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>