Find out which object was clicked on a html5 Canvas

12,790

Solution 1

The short version is that Canvas itself keeps track of none of that.

If you don't want to reinvent the wheel, start with my sample code from the tutorial "Making and Moving Selectable Shapes on an HTML5 Canvas: A Simple Example." It provides a good introduction and platform for clicking and dragging.

Solution 2

You can actually do this pretty easily using the ctx.isPointInPath() method. It takes a point in the form of an x coordinate and a y coordinate, and checks if there is some path in the canvas that contains that point within it.

So, how can you use this to figure out which element that point falls within? Well, when the canvas is cleared, there will obviously be no points left on the canvas, and hence ctx.isPointInPath() will always return false. Then, we start adding each element to the canvas, one by one. After we draw the element on the canvas, we check the function - if it returns false then the point is not within that element so we continue.

As we've been checking against each element since the canvas was blank, we know the point can't possibly be within one of the elements that we have already checked. Therefore, when we finally find an element that returns true, we know that the point must be within that particular element.

The nice part here is that we don't have to write any custom code to try and detect whether the point is within the shape, which can be tricky especially if the shape is complex - this is all handled by the browser.

There is an issue with this approach - if the point is within two or more shapes, then it will only report that it is within the first shape. A solution could be to have a seperate hidden canvas and loop through all of the shapes, clearing it after each one, and storing in an array the names of the ones where ctx.isPointInPath() returns true. There are probably other approaches you could take though. In this answer, I have focused more on the basic idea, and not a special case such as this.


Below I've got an example of this approach in action. I've set up a canvas, a script that keeps track of the mouse position, and a list of objects, each of which has a name, and a draw function which draws it to the canvas. You could imagine an object-orientated approach to this, where it is just a list of objects that all extend some AbstractShape class, but I've kept things simple for the demo.

I then simply redraw the canvas constantly (like you would anyway if you were animating it, making a game, etc) and each time use the algorithm I defined above to check if the mouse pointer is in that shape.

//Define the objects to draw on the canvas
let objects = [
  {"name":"Circle", "draw":()=>{
    ctx.beginPath();
    ctx.arc(200, 75, 50, 0, 2 * Math.PI);
    ctx.fill();
    ctx.closePath();
  }},
  {"name":"Square", "draw":()=>{
     ctx.rect(10, 10, 100, 100);
     ctx.fill();
  }}
];

//Keep track of the mouse pointer
let mouseX, mouseY;
document.body.addEventListener("mousemove", (event)=>{
  mouseX = event.clientX;
  mouseY = event.clientY;
});

//Find the canvas, its context, and the output element
let cv = document.getElementById("cv");
let ctx = cv.getContext("2d");
let output = document.getElementById("output");

//Draw the objects on the canvas
function draw() {

  //Clear the canvas
  ctx.clearRect(0, 0, cv.width, cv.height);
  
  //Clear the output div
  output.innerHTML = "";
  
  //This variable is true until we find an object
  let noObjectFound = true;
  
  //Loop through all of the objects
  for (let object of objects) {
    //Draw the current object
    object.draw();
    //Check if the mouse is on top of it
    if (noObjectFound && ctx.isPointInPath(mouseX, mouseY)) {
      //Update the output div
      output.innerHTML = object.name;
      noObjectFound = false;
    }
  }
  
  //Call again ASAP
  window.requestAnimationFrame(draw);
}

window.requestAnimationFrame(draw);
body {
  padding:0;
  margin:0;
}

#cv {
  background-color:#eee;
}
<canvas id="cv"></canvas>
<h1 id="output"></h1>

Solution 3

Unfortunately there is no object model for stuff you render into a canvas. You would need to keep track of everything you've drawn and fire separate events when you detect clicks, mouseup, mousedown, drags. It would mean creating a wrapper for all the context methods, storing the lines/images and their attributes into a COM (Canvas Object Model :) and firing events for each of the lines/images

I haven't seen anything out there that does it. This can be a lot of work, I've resorted to writing custom code everytime I need to add interactivity to a Canvas

Solution 4

For a Visio style app, or anything where interactivity with the drawn objects is important, you're better off with SVG. There are already some open source projects which you could use as a starting point. The problem with SVG is, as with any DOM heavy stuff, performance can degrade sharply once you have a large number of objects to manipulate.

Share:
12,790
Dribbel
Author by

Dribbel

Updated on July 25, 2022

Comments

  • Dribbel
    Dribbel almost 2 years

    Suppose I have a html5/canvas application in which I can place objects on a drawing-canvas. Some kind of diagram editor, something like Visio (but much simpler)

    Is there a framework which helps me find which object is clicked/dragged?

    An option is to capture the click-event and iterate over all my objects (in a semi-smart way) and check if it is clicked/dragged, but I hate to reinvent the wheel!