Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
180 views
in Technique[技术] by (71.8m points)

reactjs - Resizing shapes in a canvas via drawing a selection rectangle around a shape

I am trying to build a whiteboard app using canvas in react. so far I am able to create line and rectangle and move them on selection what I am aiming next is to resize shapes which will includes oval also. I think if I can draw a surrounding rectangle and know its coordinates then I'll check mouse is near to which corner or side and resize the shape inside accordingly but so far no good solution to the problem. If anybody has a better approach please suggest. My resize approach is similar to this site excalidraw

here is the code I have so far :

import React, { useRef, useState, useEffect } from 'react';

export interface SketchProps {

}

interface Point {
    x: number,
    y: number
}

interface IElement {
    id: number;
    X1: number;
    Y1: number;
    X2: number;
    Y2: number;
    type: string;
}

interface ISelectedData {
    selectedElement: IElement;
    selectedStartingPoint: Point;
}

const Sketch: React.SFC<SketchProps> = () => {

    const tools = {
        Line: 'line',
        Rectangle: 'rectangle',
        Selection: 'selection'
    }

    const actions = {
        Drawing: 'drawing',
        Moving: 'moving'
    }

    const [currentAction, setCurrentAction] = useState<string>('');
    const [currentTool, setCurrentTool] = useState<string>(tools.Line);
    const [selectedData, setSelectedData] = useState<ISelectedData | null>(null);

    const canvasRef: any = useRef(null);
    const contextRef: any = useRef(null);

    const [elementArray, setElementArray] = useState<IElement[]>([]);



    const createElement = (x1: number, y1: number, x2: number, y2: number) => {
        return {
            id: elementArray.length,
            X1: x1,
            Y1: y1,
            X2: x2,
            Y2: y2,
            type: currentTool
        }
    }

    const updateElement = (id: number, x1: number, y1: number, x2: number, y2: number, type: string) => {
        let currentElement = { ...elementArray[id] };
        currentElement.X1 = x1;
        currentElement.Y1 = y1;
        currentElement.X2 = x2;
        currentElement.Y2 = y2;

        let elementArrayClone = [...elementArray];
        elementArrayClone[id] = currentElement;

        setElementArray(elementArrayClone);
    }


    useEffect(() => {
        let canvas: any = canvasRef.current

        if (canvas !== null || canvas !== undefined) {
            canvas.height = window.innerHeight;
            canvas.width = window.innerWidth;

            let context: any = canvas.getContext('2d');
            if (context !== null || context !== undefined) {
                context.strokeStyle = 'black';
                context.lineWidth = 5;
                contextRef.current = context;
            }

        }
    }, [])

    const clearCanvas = () => {
        contextRef.current.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
    }

    const drawLine = (element: IElement) => {
        contextRef.current.beginPath();
        contextRef.current.moveTo(element.X1, element.Y1);
        contextRef.current.lineTo(element.X2, element.Y2);
        contextRef.current.stroke();
        contextRef.current.closePath();
    }

    const drawRectangle = (element: IElement) => {
        contextRef.current.beginPath();
        contextRef.current.rect(element.X1, element.Y1, element.X2 - element.X1, element.Y2 - element.Y1);
        contextRef.current.stroke();
        contextRef.current.closePath();
    }

    const distance = (A: Point, B: Point) => {
        return Math.sqrt(Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2))
    }

    const checkRectangle = (x: number, y: number, element: IElement) => {
        let minX = Math.min(element.X1, element.X2);
        let maxX = Math.max(element.X1, element.X2);
        let minY = Math.min(element.Y1, element.Y2);
        let maxY = Math.max(element.Y1, element.Y2);

        return x >= minX && x <= maxX && y >= minY && y <= maxY;
    }

    const checkLine = (x1: number, y1: number, element: IElement) => {

        const a: Point = { x: element.X1, y: element.Y1 };
        const b: Point = { x: element.X2, y: element.Y2 };
        const c: Point = { x: x1, y: y1 }
        let offset = distance(a, b) - (distance(a, c) + distance(c, b));
        console.log('offset', offset);
        return Math.abs(offset) < 1;
    }

    const getElementAtPosition = (x: number, y: number) => {
        for (let i = 0; i < elementArray.length; i++) {
            if (elementArray[i].type === tools.Line) {
                if (checkLine(x, y, elementArray[i]))
                    return elementArray[i];
            }
            else if (elementArray[i].type === tools.Rectangle) {
                if (checkRectangle(x, y, elementArray[i]))
                    return elementArray[i];
            }

        }
    }

    useEffect(() => {
        console.log('new render');

        clearCanvas();

        console.log('element array ', elementArray);

        elementArray.map((element) => {
            switch (element.type) {
                case tools.Line:
                    drawLine(element);
                    break;
                case tools.Rectangle:
                    drawRectangle(element);
                    break;
                default:
                    drawLine(element);
                    break;
            }
        })
    }, [elementArray])

    const handleMouseDown = ({ nativeEvent }: any) => {
        const { offsetX, offsetY } = nativeEvent;

        if (currentTool === tools.Selection) {
            let el = getElementAtPosition(offsetX, offsetY);
            console.log('el', el);
            if (el) {
                setSelectedData({
                    selectedElement: el,
                    selectedStartingPoint: { x: offsetX, y: offsetY }
                });
                setCurrentAction(actions.Moving);
            }
        }
        else {
            setCurrentAction(actions.Drawing);
            const element = createElement(offsetX, offsetY, offsetX, offsetY);
            setElementArray((prev) => {
                return (
                    [...prev, element]
                )
            });
        }

    }

    const handleMouseMove = ({ nativeEvent }: any) => {

        const { offsetX, offsetY } = nativeEvent;

        if (currentAction === actions.Drawing) {
            let index = elementArray.length - 1;
            let currentElement = { ...elementArray[index] };

            updateElement(index, currentElement.X1, currentElement.Y1, offsetX, offsetY, currentElement.type);
        }
        else if (currentAction === actions.Moving) {
            console.log('moving');

            if (selectedData !== null && selectedData !== undefined) {
                let sElement = selectedData?.selectedElement
                let sStartingPoint = selectedData?.selectedStartingPoint

                let newX1 = offsetX - (sStartingPoint.x - sElement?.X1)
                let newX2 = offsetX - (sStartingPoint.x - sElement?.X2)
                let newY1 = offsetY - (sStartingPoint.y - sElement?.Y1)
                let newY2 = offsetY - (sStartingPoint.y - sElement?.Y2)

                updateElement(sElement.id, newX1, newY1, newX2, newY2, sElement.type);
            }


        }
    }

    const handleMouseUp = () => {
        setCurrentAction('');
        setSelectedData(null);
    }

    return (
        <div>
            <canvas
                ref={canvasRef}
                onMouseDown={handleMouseDown}
                onMouseUp={handleMouseUp}
                onMouseMove={handleMouseMove}
            >
                Canvas
        </canvas>
            <button onClick={() => {
                setCurrentTool(tools.Selection);
            }}>select</button>
            <button onClick={() => {
                setElementArray([]);
                clearCanvas()
            }}>reset</button>
            <button onClick={() => {
                setCurrentTool(tools.Line);
            }}>line</button>
            <button onClick={() => {
                setCurrentTool(tools.Rectangle);
            }}>rectangle</button>
        </div>
    );
}

export default Sketch; 
question from:https://stackoverflow.com/questions/65879614/resizing-shapes-in-a-canvas-via-drawing-a-selection-rectangle-around-a-shape

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...