I have a React+Typescript web application and I am trying to draw rectangles on the canvas. I want to use React hooks and avoid classes if possible.
Expected result is to be able to draw as many rectangles as is needed within the canvas when mouse is pressed down and is moving. Also I would like to extend this later on to allow dragging, resizing and different shapes.
Currently I am able to draw rectangles however as mouse moves context keep adding rectangles as oppose updating size of the original one.
I saw few questions related to drawing rectangles however I didn't see any of them using React hooks, that's why I decided to create question.
This is the code which am using at the moment and which gives me result from above.
import React, { useEffect, useRef, useState } from 'react';
interface CanvasProps {
width: number;
height: number;
}
type Coordinate = {
x: number;
y: number;
};
type Rectangle = {
start: Coordinate;
completed: boolean;
end?: Coordinate;
};
const Canvas = ({ width, height }: CanvasProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [currentMousePosition, setCurrentMousePosition] = useState<Coordinate>({ x: 0, y: 0 });
const [isMouseDown, setMouseDown] = useState(false);
const [activeDrawing, setActiveDrawing] = useState<Rectangle>();
const onMouseMove = (event: MouseEvent) => {
setCurrentMousePosition({
x: event.clientX,
y: event.clientY,
});
};
const triggerMouseDown = (event: MouseEvent) => {
if (!canvasRef.current) return;
let boundingRect = canvasRef.current.getBoundingClientRect();
if (!isMouseDown) {
setMouseDown(true);
//check is there active drawing
if (!activeDrawing)
setActiveDrawing({
start: {
x: event.clientX - boundingRect.left,
y: event.clientY - boundingRect.top,
},
completed: false,
});
}
setCurrentMousePosition({
x: event.clientX - boundingRect.left,
y: event.clientY - boundingRect.top,
});
};
const triggerMouseUp = () => {
setMouseDown(false);
setActiveDrawing(undefined);
draw();
};
const draw = () => {
const canvas: HTMLCanvasElement = canvasRef.current;
const context = canvas.getContext('2d');
if (context && activeDrawing?.start) {
context.putImageData(
context.getImageData(0, 0, context.canvas.clientWidth, context.canvas.clientHeight),
0,
0
);
context.save();
const startX =
currentMousePosition.x < activeDrawing.start.x ? currentMousePosition.x : activeDrawing.start.x;
const startY =
currentMousePosition.y < activeDrawing.start.y ? currentMousePosition.y : activeDrawing.start.y;
const widthX = Math.abs(activeDrawing.start.x - currentMousePosition.x);
const widthY = Math.abs(activeDrawing.start.y - currentMousePosition.y);
// context.beginPath();
console.info('Current Mouse Position', currentMousePosition);
console.info('Active drawing', activeDrawing);
context.rect(startX, startY, widthX, widthY);
context.stroke();
context.restore();
}
};
useEffect(() => {
if (!isMouseDown) return;
if (!canvasRef.current) {
return;
}
draw();
}, [isMouseDown, currentMousePosition]);
useEffect(() => {
if (!canvasRef.current) {
return;
}
const canvas: HTMLCanvasElement = canvasRef.current;
canvas.addEventListener('mouseup', triggerMouseUp);
canvas.addEventListener('mousedown', triggerMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
return () => {
canvas.removeEventListener('mouseup', triggerMouseUp);
canvas.removeEventListener('mouseleave', triggerMouseDown);
canvas.removeEventListener('mousemove', onMouseMove);
};
}, []);
const getCoordinates = (event: MouseEvent): Coordinate | undefined => {
if (!canvasRef.current) {
return;
}
const canvas: HTMLCanvasElement = canvasRef.current;
return { x: event.pageX - canvas.offsetLeft, y: event.pageY - canvas.offsetTop };
};
return <canvas ref={canvasRef} height={height} width={width} />;
};
Canvas.defaultProps = {
width: window.innerWidth,
height: window.innerHeight,
};
export default Canvas;
There is also codesandbox where you can see code and actual result.
