How to calculate the position of a click on a draggable canvas element?

I need help with a pretty difficult problem. I am currently making a game with React and Redux. In this game I use a canvas to create a map data from my redux store. Currently the map is just a matrix of black and white squares. Let's say that I wanted to change the color of a square when you click on it, but also maintain the ability to drag the element. The problem is that it is difficult to pinpoint exactly where the mouse is clicking given that the element can be dragged anywhere on the page. As far as I can tell, none of the mouseclick event object properties seem to be able to tell me. I thought offsetX and offsetY would tell me, but they don't seem to stay the same when the canvas object moves for some reason.

I am using React and Redux for this project, and the CanvasMap element is wrapped in a Draggable from this library: https://www.npmjs.com/package/react-draggable#draggablecore

import React from 'react'
import { connect } from 'react-redux'
import './CanvasMap.css'

class CanvasMap extends React.Component{

  componentDidMount() {
    const map = this.props.map //Data representing the map
    const canvas = this.refs.canvas
    const context = canvas.getContext('2d')

    //Building the map on the canvas
    for(let i = 0; i < map.length; i++){
      for(let j = 0; j < map[i].length; j++){
        const x=i*100
        const y=j*100
        const isBlackSquare= map[i][j] === 'black' ? true : false
        if(isBlackSquare) context.fillRect(x, y, 100, 100)
        else context.strokeRect(x, y, 100, 100)
      }
    }

    function handleClick(e){
      //None of the event properties seem to stay the same when the canvas is moved
      console.log(e)
    }


    canvas.addEventListener('click', handleClick)
  }

  render(){
    return (
      <div className='Map'>
        <canvas ref='canvas' width={8000} height={8000} />
      </div>
    )
  }
}

function mapStateToProps(state){
  return {
    map: [...state.map]
  }
}

const connectedComponent = connect(mapStateToProps)(CanvasMap)
export { connectedComponent as CanvasMap }

Upvotes: 1

Views: 1285

Answers (2)

SOLVED IT!

I got lost in the slew of data around elements and click events, I was trying to figure out the right combination of pageX, clientX, offsetLeft, screenX, etc. However, the final solution is incredibly simple once you know exactly what to do. Here it is:

function handleClick(e){
  const rect = e.target.getBoundingClientRect()
  const x = e.pageX - rect.left
  const y = e.pageY - rect.top
}

This should get you the exact x and y coordinate of your mouse in relation to the element, no matter where you drag and reposition the element.

Upvotes: 2

JonoJames
JonoJames

Reputation: 1223

In most cases when you click on an HTML element you can use the rectangleBounding Box and get coordinators from that like

domRect = element.getBoundingClientRect();

in a canvas click position it is a little more difficult

Here is a script I did a while ago to draw while dragging the mouse the on the canvas .Maybe you can apply this method

<html>
    <head>
    <style>

    * { margin:0; padding:0; } /* to remove the top and left whitespace */

    html, body { width:100%; height:100%; } /* just to be sure these are full screen*/

    canvas { display:block; } /* To remove the scrollbars */



    </style>
    </head>

    <body>
    <canvas id="canvas" ></canvas>
    <script>



    ////////////////////////////////////////


    (function() {
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');

        var elemLeft = canvas.offsetLeft;
        var elemTop = canvas.offsetTop;
        var BB=canvas.getBoundingClientRect();
        var offsetX=BB.left;
        var offsetY=BB.top;

        // resize the canvas to fill browser window dynamically
        window.addEventListener('resize', resizeCanvas, false);

        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;

            /**
                * Your drawings need to be inside this function otherwise they will be reset when 
                * you resize the browser window and the canvas goes will be cleared.
            */
            drawStuff(); 
        }
        resizeCanvas();

        function drawStuff() {
            // do your drawing stuff here
            var img = new Image();              

            img.src = 'images/3PkBe.gif';
            img.onload = function()
            {
                //var canvas = document.getElementById('canvas');
                // create pattern
                var ptrn = ctx.createPattern(img, 'repeat'); // Create a pattern with this image, and set it to "repeat".
                ctx.fillStyle = ptrn;
                ctx.fillRect(0, 0, canvas.width, canvas.height); // context.fillRect(x, y, width, height);
                ctx.shadowBlur=20;
                //ctx.shadowColor="black";
                //ctx.fillStyle="green";
                //ctx.fillRect(20,160,100,80);


                ctx.strokeStyle = "lightgray";

                //var canvasOffset = canvas.offset();
                //var offsetX = canvasOffset.left;
                //var offsetY = canvasOffset.top;

                var mouseIsDown = false;
                var lastX = 0;
                var lastY = 0;
                var elements = [];

                makeShip( 30 , 30,120, 120,  '#119' , "romea");
                makeShip( 30, 160,120, 120,  '#393', "fomar");
                makeShip( 30, 290,120, 120,  '#955', "ojab");
                makeShip( 30, 420,120, 120,  '#6ff', "eliot");
                // Add event listener for `click` events.

                canvas.addEventListener('click', function(event) {

                    var x = event.pageX - elemLeft,
                    y = event.pageY - elemTop;
                    console.info(x, y);
                    elements.forEach(function(element) {

                        if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
                            console.log(element.name);
                        }
                    });





                }, false);

                canvas.addEventListener('mousedown', function(event) {

                        var x = event.pageX - elemLeft,
                    y = event.pageY - elemTop;
                    console.info(x, y);
                    elements.forEach(function(element) {

                        if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
                            console.info(element.name);
                            handleMouseDown(element);
                        }
                    });



                }, false);

                canvas.addEventListener('mousemove', function(event) {


                    var x = event.pageX - elemLeft,
                    y = event.pageY - elemTop;
                    console.info(x, y);
                    elements.forEach(function(element) {

                        if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
                            console.info(element.name);
                            handleMouseMove(element,x,y);
                        }
                    });

                }, false);

                canvas.addEventListener('mouseup', function(event) {


                    var x = event.pageX - elemLeft,
                    y = event.pageY - elemTop;
                    //console.info(x, y);

                    elements.forEach(function(element) {

                        //if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
                            console.info(element.name + "mouse up evenr=========");
                            handleMouseUp(element);
                    //}
                    });

                }, false);




                function makeShip(x, y, width, height, colour,ShipName) {
                    var ship = {
                        name: ShipName,
                        colour: colour,
                        width: width,
                        height: height,
                        x: x,
                        y: y
                    }
                    elements.push(ship);
                    return (ship);
                }
                function drawShip(ship) {

                    //ctx.fillStyle = ship.colour;
                    //ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
                    //ctx.fillRect(element.x, element.y, element.width, element.height);
                }

                function drawAllShips() {
                    //  ctx.clearRect(0, 0, canvas.width, canvas.height);
                    for (var i = 0; i < elements.length; i++) {
                        var ship = elements[i]
                        //drawShip(ship);
                    ctx.fillStyle = ship.colour;
                    ctx.fillRect(ship.x , ship.y, ship.width, ship.height);
                        //   ctx.fillStyle = ship.fill;
                        //   ctx.fill();
                        //    ctx.stroke();
                    }
                }




                // Add element.
                //elements.push({
                //colour: '#05EFFF',
                //width: 150,
                //height: 100,
                //x: 20,
                //y: 15
            //});

            // Render elements.
            //  elements.forEach(function(element) {
            //      ctx.fillStyle = element.colour;
            //      ctx.fillRect(element.x, element.y, element.width, element.height);
            //  });

            drawAllShips();


            function handleMouseDown(e) {
              mouseX = e.x ;
              mouseY = e.y ;

                //mouseX = parseInt(e.x - offsetX);
                //mouseY = parseInt(e.y - offsetY);
                console.log("===========Problem "+mouseX);
                // mousedown stuff here
                lastX = mouseX;
                lastY = mouseY;
                mouseIsDown = true;
                //alert("mouse Handle");

            }

            function handleMouseUp(e) {
                //mouseX = parseInt(e.clientX - offsetX);
                //mouseY = parseInt(e.clientY - offsetY);

     ctx.onmousemove = null;
                // mouseup stuff here
                mouseIsDown = false;
                return
            }

            function handleMouseMove(e,x,y) {
                if (mouseIsDown) {



                //console.log(' no fuck');
                mouseX = e.x ;
                mouseY = e.y ;
                console.log(e.name+"is truing to drag");

                // mousemove stuff here
                //for (var i = 0; i < elements.length; i++) {

                        //if (ctx.isPointInPath(mouseX, mouseY)) {
                    //console.log('============== no fuck');
                    var ship =e;// elements[i];
                    ship.x = x-15;//(mouseX - lastX);
                    ship.y = y-20;//(mouseY -lastY);
                //  ship.right = ship.x + ship.width;
                //  ship.bottom = ship.y + ship.height;

                    //drawShip(ship);
            //}

            //}
                lastX = mouseX;
                lastY = mouseY;
                drawAllShips(); 
                }
            }

            <!-- ctx.mousedown(function (e) { -->
                <!-- handleMouseDown(e); -->
            <!-- }); -->
            <!-- ctx.mousemove(function (e) { -->
                <!-- handleMouseMove(e); -->
            <!-- }); -->
            <!-- ctx.mouseup(function (e) { -->
                <!-- handleMouseUp(e); -->
            <!-- }); -->

        }



    }
    })();
    </script>
    </body>
    </html>         

Upvotes: 1

Related Questions