Search code examples
drag-and-drophtml5-canvas

Shapes drawn on canvas moves too far when drag and drop event


I am facing an issue when dragging a rectangle, if I moved it just a little than it will redrawn too far from the current new mouse position. How do I correct the distance calculation?

<!DOCTYPE html>
<html>
    <meta charset="utf-8">
    <head>

    </head>
    <body>
        <canvas id="face-canvas" style="border:1px solid #000000;"></canvas>

        <script type="module">
            (() => {
                let canvas = document.getElementById("face-canvas");
                canvas.height = 300;
                canvas.width = 300;
                let cnt = canvas.getContext("2d");

                let isDragging = false;
                let offsetX = canvas.getBoundingClientRect().left;
                let offsetY = canvas.getBoundingClientRect().top;
                let startX = 0, startY = 0;
                let selectedObjIndex = 0;
                let drawn_obj = [];
                drawn_obj.push({...drawBox(20, 15, 50, 50, "#1df"), index: 1})
                drawn_obj.push({...drawBox(80, 40, 50, 50, "#a33"), index: 2})




                document.addEventListener("mousedown", (event) => onMouseDown(event));
                document.addEventListener("mousemove", (event) => onMouseMove(event));
                document.addEventListener("mouseup", (event) => onMouseUp(event));


                function onMouseDown(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    startX = Math.abs(parseInt(event.clientX - offsetX));
                    startY = Math.abs(parseInt(event.clientY - offsetY));
                    selectedObjIndex = selectedObj(startX, startY);
                    if(selectedObjIndex > 0) {
                        isDragging = true;
                        console.log("ffffffff")
                    }
                }

                function onMouseMove(event) {
                    if(isDragging && selectedObjIndex > 0) {
                        let moveX = parseInt(event.clientX - offsetX);
                        let moveY = parseInt(event.clientY - offsetY);
                        let newX = parseInt(moveX - startX);
                        let newY = parseInt(moveY - startY);
                        drawn_obj[selectedObjIndex - 1].x += newX; 
                        drawn_obj[selectedObjIndex - 1].y += newY;
                        console.log("newX, newY", newX, newY)

                        redrawAll();
                    }
                }
                
                function onMouseUp() {
                    event.preventDefault();
                    event.stopPropagation();
                    isDragging = false;
                }

                function selectedObj(x, y) {
                    for(let i = 0; i < drawn_obj.length; ++i) {
                        if(x >= drawn_obj[i].x && x <= (drawn_obj[i].x + drawn_obj[i].w) && 
                            y >= drawn_obj[i].y && y <= (drawn_obj[i].y + drawn_obj[i].h)) {
                            return drawn_obj[i].index;
                        }
                    }

                    return 0;
                }

                function redrawAll() {
                    cnt.clearRect(0, 0, canvas.width, canvas.height);
                    for(let i = 0; i < drawn_obj.length; ++i) {
                        drawBox(drawn_obj[i].x, drawn_obj[i].y, drawn_obj[i].w, drawn_obj[i].h, drawn_obj[i].color);
                    }
                }

                function drawBox(x, y, w, h, color) {
                    cnt.fillStyle = color;
                    cnt.fillRect(x, y, w, h);
                    return {x: x, y: y, w: w, h: h, color: color}
                }
            })();
        </script>
    </body>
</html>

Solution

  • Here's what you can do:

    1. In the top level function add two variables for the offset inside the object:
    let objectOffsetX = 0;
    let objectOffsetY = 0;
    
    1. Calculate them when the click event is received:
    function onMouseDown(event) {
        event.preventDefault();
        event.stopPropagation();
        startX = event.clientX - offsetX;
        startY = event.clientY - offsetY;
    
        selectedObjIndex = selectedObj(startX, startY);
    
        objectOffsetX = startX - drawn_obj[selectedObjIndex - 1].x;
        objectOffsetY = startY - drawn_obj[selectedObjIndex - 1].y;
    
        if(selectedObjIndex > 0) {
            isDragging = true;
            console.log("ffffffff")
        }
    }
    
    1. When the mouse moves calculate the new object position using the canvas offset and the object offset:
    function onMouseMove(event) {
        if(isDragging && selectedObjIndex > 0) {
            let newX = event.clientX - offsetX - objectOffsetX;
            let newY = event.layerY - offsetY - objectOffsetY;
            drawn_obj[selectedObjIndex - 1].x = newX;
            drawn_obj[selectedObjIndex - 1].y = newY;
            console.log("newX, newY", newX, newY)
    
            redrawAll();
        }
    }
    

    You may notice that I simplified some code, you don't really need to use parseInt, you've already got everything as (integer) numbers, not strings.

    You can see the whole thing working on codepen