Prerequisites:
Safari HTML5 Canvas Guide, “Adding Mouse and Touch Controls to Canvas. The material on iOS-specific controls is optional for this course, though certainly worth knowing if you intend to develop for an iPad, iPhone, or other iOS device.
In many cases, we would like the user to interact with the canvas, not just look at a picture or animation. This calls for recognizing and processing events (objects which represent user actions such as mouse movement, key click, or activating a button on a form) and, sometimes, setting up controls (buttons or other user input widgets in a form).
We will begin with some low-level events, which do not require forms, just keyboard and mouse.
The DOM Level 3 Events Specification defines 9 kinds of mouse events; here are 5 of them:
mousemove - the mouse has moved while it is over an element.mousedown - a mouse button has been pressed down while over an element.mouseup - a mouse button has been released while over an element.click - a mouse button has been pressed down and then released while over an element.dblclick - a mouse button has been “double clicked” while over an element.We can query each of these events to determine where the mouse is which button(s) changed state or are held down, and other information.
Suppose we want to print, on the console log, the location of the mouse as it moves over the canvas.
We need to define an event handler function, and we need to set it as an event listener for the canvas.
Here’s the event handler function:
function handleMouseMoved (e) {
console.log("Mouse moved to " + [e.clientX, e.clientY]);
}The parameter e is the mouse event. Its properties clientX and clientY are the coordinates of the mouse pointer, at the time of the event. These are given as client coordinates, i.e., relative to the top left corner of the web browser window. The alternative is screen coordinates, e.screenX and e.screenY, which are relative to the top left corner of the root window, i.e., the display device. There is no mouse event property which directly gives us the location in canvas coordinates. This is a bit awkward, but we can work around it.
To set the function as a “mousemoved” handler for the canvas, we can write
canvas.addEventListener("mousemove", handleMouseMoved, false);or
canvas.onmousemove = handleMouseMoved;The former is recommended, but in my testing (July–August, 2012) it did not always work—possibly because I was using addEventHandler instead of addEventListener.1
If we wanted to print mouse clicks including the button number, we could do this:
canvas.onclick = function (e) {
var b = e.button;
var x = e.clientX, y = e.clientY;
console.log("clicked button " + b + " at " + [x, y];
}Mouse buttons are numbered from 0, so that on a typical wheel mouse, 0 is the left button, 1 is the scroll wheel, and 2 is the right button.
Note that a user’s clicking the mouse button typically results in three events: mousedown, mouseup, and then click.
The DOM Level 3 Events Specification defines 3 kinds of keyboard events:
keydown: a key has been pressed down.keypress: a key which normally produces a character value has been pressed down.keyup: a key has been released.Pressing and releasing the “j” key typically results in three events: keydown, keypress, and then keyup. But pressing and releasing the left Ctrl key should result in just two events: keydown, keyup; there is no keypress event because the Ctrl key does not, by itself, produce a character value.
Keyboard events have several properties, of which the char and key properties are most interesting to us—or ought to be in the future. The key property identifies the key, and the char property is the character value of the key, but only if it has a character value (so, again, “j” has a character value, but left-Ctrl does not). Currently (August, 2012), neither Firefox nor Chromium implements these properties properly (they have undefined values), and instead we have to use the deprecated keyCode property, which is a numerical code identifying the key. We can use String.fromCharCode(e.keyCode) to get the character, provided the key generates a character; if it does not, we get an empty string.
Chase the mouse.
As the user moves the mouse over the canvas, a “chaser” (represented by a red circle) will attempt to move to the mouse; when it arrives, it turns green. The user can press ‘S’ to spawn additional chasers, and ‘K’ to “kill” a chaser.
We’ll use variables mx and my to track the mouse position:
// Mouse
var mx, my; // mouse position in canvas coordinatesIn our mouse moved event handler, we’ll extract the mouse’s position using e.clientX and e.clientY. But remember, these are relative to the top left of the web page, and we have to adjust them to get canvas coordinates. We subtract canvas.offsetLeft and canvas.offsetTop to adjust for the canvas’s position within the page. That’s fine unless the page has been scrolled, but if it has, then we also have to add window.pageXOffset and window.pageYOffset to compensate for the scroll bar positions.
function mouseMoved (e) {
// update global mx, my
mx = e.clientX - canvas.offsetLeft + window.pageXOffset;
my = e.clientY - canvas.offsetTop + window.pageYOffset;
}
...
canvas.onmousemove = mouseMoved;Our keyup event handler gets the old-fashioned e.keyCode property, but also logs the new-fangled e.char and e.key properties, which we should use when they become implemented.
We can’t set the onkeyup property of the canvas (or if we do, it doesn’t have any effect), but we can do it on the window or on the document object. Doing this, however, means that the keystrokes will be handled even when we are not focused on the canvas.
function keyUp (e) {
var kc = e.keyCode;
var ks = String.fromCharCode(e.keyCode);
var kchar = e.char,
kkey = e.key;
console.log("keyUp: " + [kc, typeof(kc), ks, typeof(ks),
kchar, typeof(kchar), kkey, typeof(kkey)]);
if (ks === "K")
killChaser();
else if (ks === "S")
spawnChaser();
}
...
window.onkeyup = keyUp;We use an array to store the chasers; the functions spawnChaser and killChaser push and pop chaser objects onto or off of the array:
// Chasers
var chasers = [];
...
function spawnChaser () {
chasers.push(makeChaser());
renderAll();
}
function killChaser () {
if (chasers.length > 0)
chasers.pop();
renderAll();
}Here is our complete solution:
Sometimes we need more to our user interface than just mouse and keyboard actions—and that means bringing in forms with buttons, text inputs, and other sorts of widgets.
These are HTML elements, so we’ll create them with HTML code:
<form>
<p>
<input id="randColor" type="button" value="Random color"
title="Randomize hat color"/>
<label>Hat size
<input id="hatSize" type="text" value="10"/>
</label>
</p>
</form>Event handlers can be set in HTML, like this:
<input type="button" value="Go" onclick="HANDLERFUNCTION"/>However, this requires that HANDLERFUNCTION be exposed at the top level of the script; I prefer set the event handler in JavaScript and to hide its definition in a closure.
Revise the orbiting square example, with controls for size and speed, and to start and stop the animation.
If you’re familiar with CSS, you can use it to position and style the controls—you can even position the controls over the canvas.
There are subtle differences: with addEventListener we do not displace an existing handler for the same event, if there is any; addEventListener also has a third argument of boolean type which controls how events are “captured.” These details need not concern us here.↩︎