If you need to interact with a specific object in the canvas you have to face a problem.
Using DOM you can click on a node and the browser manages the whole interaction (triggers the event, puts the DOM node in event.target, etc.).
But, as explained before, once you have drawn an object to a canvas it become a part of the canvas. So the question is: how to detect when a user click on a specific shape?
Getting canvas related coordinates
The first obstacle to get through is getting where a user clicked on the canvas. When a user clicks you can get the mouse coordinates relative to the page and you must subtract the canvas position:
canvas.addEventListener('click', function (evt){ var rect = canvas.getBoundingClientRect(), posx = evt.clientX - rect.left, posy = evt.clientY - rect.top; ...
It's not too complicate, isn't it?
Checking in reverse order
var objs = [ {name:'shape2', color:'green', x:30, y:50, angle:Math.PI/2, width: 40, height: 50 }, {name:'shape1', color:'red', x:40, y:30, angle:Math.PI/4, width: 30, height: 40 } ];
Then each shape is drawn in order over the previous.
For this reason I have to check shapes in reverse order, from the object in foreground to the one in background.
for (i = objs.length - 1; i >= 0; i--){ obj = objs[i]; ...
Pointer transformations
Now I have pointer coordinates and shape transformations:
How to detect if pointer coordinates are inside this shape?
My solution is a bit imaginative but it seems to work well.
{name:'shape1', color:'red', x:40, // translation x y:30, // translation y angle:Math.PI/4, // rotation width: 30, height: 40 }
How to detect if pointer coordinates are inside this shape?
My solution is a bit imaginative but it seems to work well.
I apply transformations to pointer coordinates. These are the opposite that I have applied to the object before drawing it to the canvas. Then I check if the new pointer coordinates are inside the object shape pretending that the shape has drawn around 0, 0.
// pointer transformation p = new Point(posx, posy); p.translate(-obj.x, -obj.y); p.rotate(-obj.angle); if (p.x > -(obj.width / 2) && p.x < obj.width / 2 && p.y > - (obj.height / 2) && p.y < obj.height / 2){ log.innerHTML = 'clicked on ' + obj.name; return; }
I wrote this simple object to help with transformations (see part 1 for an explanation on transformations):
var Point = function (x, y){ this.x = x; this.y = y; }; Point.prototype.rotate = function (angle){ var x, y; x = this.x*Math.cos(angle) - this.y * Math.sin(angle); y = this.x*Math.sin(angle) + this.y * Math.cos(angle); this.x = x; this.y = y; return this; }; Point.prototype.translate = function (x, y){ this.x = this.x + x; this.y = this.y + y; return this; };
This is the whole code:
canvas.addEventListener('click', function (evt){ var rect = canvas.getBoundingClientRect(), posx = evt.clientX - rect.left, posy = evt.clientY - rect.top, i, obj,p; // I get the position of the pointer // relative to the canvas for (i = objs.length - 1; i >= 0; i--){ // cycling in inverse order obj = objs[i]; // pointer transformation p = new Point(posx, posy); p.translate(-obj.x, -obj.y); p.rotate(-obj.angle); if (p.x > -(obj.width / 2) && p.x < obj.width / 2 && p.y > - (obj.height / 2) && p.y < obj.height / 2){ log.innerHTML = 'clicked on ' + obj.name; return; } } log.innerHTML = ''; }, false);
This is the result (try to click on a shape):