Today we do a bit more with the mouse. In particular, interacting with existing objects on the canvas. If you are used to Flash, dealing with mouse interaction with canvas objects may come across as very counterintuitive. This is because there are no real canvas objects. A canvas is actually more akin to a simple bitmap in Flash. You can see stuff you’ve drawn on it, but they are just pixels. There is no concept of a display list like you have in Flash. So you can’t natively say that you want to be notified when the user clicks on this red circle over on the left side of the canvas.
I know a few people who have come to JavaScript from Flash who have attempted to create frameworks that recreate the Flash display list for canvas. These may work fine. I haven’t used them personally, but my personal feeling is that this is the wrong way to approach things. I always try to take a new language for what it is, not try to force a different paradigm from another language onto it because I’m more comfortable with the other language.
However, if you have done a lot of work with Processing, you will probably be right at home with handling mouse interaction in canvas. It’s very similar.
The basic concept is that you need to model your own objects in code, then evaluate the locations of the mouse events and determine if your object should react with the mouse after any given event occurs. For example, if you want to be able to click on a circle, you have to know the center point and radius of the circle. When you get a click event, you find out how far the mouse is from the center of the circle. If the distance is less than the radius, then the user has clicked on the circle. If it’s more, than he/she has clicked outside of the circle.
This next example does just this, but with drag and drop. I’ll dump the code on you, then go through and explain it.
[php lang=”JavaScript”]$(function () {
var canvas, context, width, height, x, y, radius = 25, clickX, clickY, drag = false;
canvas = $(“#canvas”)[0];
context = canvas.getContext(“2d”);
width = canvas.width;
height = canvas.height;
x = width / 2;
y = height / 2;
draw();
$(“#canvas”).mousedown(function (event) {
var dx, dy, dist;
dx = event.pageX – this.offsetLeft – x;
dy = event.pageY – this.offsetTop – y;
dist = Math.sqrt(dx * dx + dy * dy);
if(dist < radius) {
drag = true;
clickX = dx;
clickY = dy;
}
else {
drag = false;
}
});
$("#canvas").mouseup(function (event) {
drag = false;
});
$("#canvas").mousemove(function (event) {
if(drag) {
x = event.pageX - this.offsetLeft - clickX;
y = event.pageY - this.offsetTop - clickY;
draw();
}
});
function draw() {
context.clearRect(0, 0, width, height);
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, false);
context.fill();
}
});[/php]
As I said, we need to model the object in code. This will be done by the x, y, and radius values. We'll set x and y to the center of the canvas and then call draw. The draw function simply draws a circle at the x, y point with the given radius.
Then we have handlers for the three events: mousedown, mouseup, and mousemove.
The mousedown handler does the distance check as described above. If we have a hit, it sets the drag var to true, and gets an offset value from where the mouse is to the center of the circle. We'll use that offset to drag the circle from the point where it was clicked, rather than making it snap to the center. You'll see that shortly.
The mouseup handler simply sets drag to false.
Finally, the mousemove handler does the rest. If dragging is false, it does nothing. But if true, it sets x and y to the current mouse position minus the offset value we got in mousedown. Then it calls draw again to update the position of the circle. Simple!
Try it out here: http://www.bit-101.com/jscanvas/mar27-1.html
OK, that’s great for a circle, but what if we have something rectangular that we want to interact with? Well, we just have to model that rectangle in code. The next example shows just such a thing. It’s mostly the same other than the shape and the way we test the hit. Rather than radius, we just check if the click is inside the rectangle. It’s a bit brute force, but it works.
[php lang=”JavaScript”]$(function () {
var canvas, context, width, height, rect, clickX, clickY, drag = false;
canvas = $(“#canvas”)[0];
context = canvas.getContext(“2d”);
width = canvas.width;
height = canvas.height;
rect = {x:width / 2 – 25, y:height / 2 – 25, w:50, h:50};
draw();
$(“#canvas”).mousedown(function (event) {
var x = event.pageX – this.offsetLeft, y = event.pageY – this.offsetTop;
if(x > rect.x && x < rect.x + rect.w &&
y > rect.y && y < rect.y + rect.h) {
drag = true;
clickX = x - rect.x;
clickY = y - rect.y;
}
else {
drag = false;
}
});
$("#canvas").mouseup(function (event) {
drag = false;
});
$("#canvas").mousemove(function (event) {
if(drag) {
rect.x = event.pageX - this.offsetLeft - clickX;
rect.y = event.pageY - this.offsetTop - clickY;
draw();
}
});
function draw() {
context.clearRect(0, 0, width, height);
context.fillRect(rect.x, rect.y, rect.w, rect.h);
}
});[/php]
Try it out here: http://www.bit-101.com/jscanvas/mar27-2.html
Of course, if you have some other type of shape, you might have to get a bit more creative on how you figure out whether or not you’re hitting it with the mouse. But the above handles most use cases and will get you started anyway.
First of all, thanks for posting this series of tutorials/experiments.Your AS background makes for a unique and relevant perspective.
I have a question about mouse events as they relate to Canvas and I apologize up front if it is a stupid one … but – given that the canvas is a bitmap – is there a way to retrieve the current pixels color from a mouseEvent? Something like event.pixelColor?
I realize one could create a huge lookup table for all pixels and use the position to retrieve the pixel color, but – well – that just seems a tad clunky. It would be nice if this was part of the event.
i’ll be covering pixel access tomorrow (29th). 🙂
Excellent! Thanks again!
Just for your information, if you are looking for simple mouse interaction with canvas, I really suggest you to look up KineticJS: http://kineticjs.com/
KineticJS is super fast and supports most likely all the shapes you can think of. Plus it works on mobiles, too! Check out this example: http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-multiple-shapes-with-kineticjs/