使用反应钩子在画布上绘制矩形

thu*_*ug_ 5 typescript reactjs react-hooks

我有一个 React+Typescript Web 应用程序,我正在尝试在画布上绘制矩形。我想使用 React hooks 并尽可能避免使用类。

预期结果是当鼠标按下并移动时,能够在画布内绘制所需数量的矩形。我还想稍后扩展它以允许拖动、调整大小和不同的形状。

目前我可以绘制矩形,但是当鼠标移动时,上下文会不断添加矩形,而不是更新原始矩形的大小。

绘图结果

我看到了一些与绘制矩形相关的问题,但是我没有看到任何使用 React hooks 的问题,这就是我决定创建问题的原因。

这是目前正在使用的代码,它给了我上面的结果。

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;
Run Code Online (Sandbox Code Playgroud)

还有codesandbox,您可以在其中查看代码和实际结果。

小智 1

在你的draw()函数中,尝试添加

context.clearRect(0, 0, canvas.width, canvas.height);
Run Code Online (Sandbox Code Playgroud)

从画布中清除任何不需要的矩形。之后你需要使用

context.beginPath();
Run Code Online (Sandbox Code Playgroud)

如文档中所述:请确保在调用clearRect()之后开始绘制新项目之前调用beginPath()。

完整的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();

            context.clearRect(0, 0, canvas.width, canvas.height);
            context.beginPath();

            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();
        }
    };
Run Code Online (Sandbox Code Playgroud)