- 1.1. The canvas Element
- 1.2. Canvas Contexts
- 1.3. Canonical Examples in This Book
- 1.4. Getting Started
- 1.5. Fundamental Drawing Operations
- 1.6. Event Handling
- 1.7. Saving and Restoring the Drawing Surface
- 1.8. Using HTML Elements in a Canvas
- 1.9. Printing a Canvas
- 1.10. Offscreen Canvases
- 1.11. A Brief Math Primer
- 1.12. Conclusion
1.6. Event Handling
HTML5 applications are event driven. You register event listeners with HTML elements and implement code that responds to those events. Nearly all Canvasbased applications handle either mouse or touch events—or both—and many applications also handle various events such as keystrokes and drag and drop.
1.6.1. Mouse Events
Detecting mouse events in a canvas is simple enough: You add an event listener to the canvas, and the browser invokes that listener when the event occurs. For example, you can listen to mouse down events, like this:
canvas.
onmousedown=
function
(
e)
{
// React to the mouse down event}
;
Alternatively, you can use the more generic addEventListener() method:
canvas.
addEventListener
(
'mousedown'
,
function
(
e)
{
// React to the mouse down event}
);
In addition to onmousedown, you can also assign functions to onmousemove, onmouseup, onmouseover, and onmouseout.
Assigning a function to onmousedown, onmousemove, etc., is a little simpler than using addEventListener(); however, addEventListener() is necessary when you need to attach multiple listeners to a single mouse event.
1.6.1.1. Translating Mouse Coordinates to Canvas Coordinates
The mouse coordinates in the event object that the browser passes to your event listener are window coordinates, instead of being relative to the canvas itself.
Most of the time you need to know where mouse events occur relative to the canvas, not the window, so you must convert the coordinates. For example, Figure 1.14 shows a canvas that displays an image known as a sprite sheet. Sprite sheets are a single image that contains several images for an animation. As an animation progresses, you display one image at a time from the sprite sheet, which means that you must know the exact coordinates of each image in the sprite sheet.
Figure 1.14. Sprite sheet inspector
The application shown in Figure 1.14 lets you determine the location of each image in a sprite sheet by tracking and displaying mouse coordinates. As the user moves the mouse, the application continuously updates the mouse coordinates above the sprite sheet and the guidelines.
The application adds a mousemove listener to the canvas, and subsequently, when the browser invokes that listener, the application converts the mouse coordinates from the window to the canvas, with a windowToCanvas() method, like this:
function
windowToCanvas
(
canvas,
x,
y)
{
var
bbox=
canvas.
getBoundingClientRect
();
return
{
x:
x-
bbox.
left* (
canvas.
width/
bbox.
width),
y:
y-
bbox.
top*
(
canvas.
height/
bbox.
height)
}
;
}
canvas.
onmousemove=
function
(
e)
{
var
loc=
windowToCanvas
(
canvas,
e.
clientX,
e.
clientY);
drawBackground
();
drawSpritesheet
();
drawGuidelines
(
loc.
x,
loc.
y);
updateReadout
(
loc.
x,
loc.
y);
}
;
...
The windowToCanvas() method shown above invokes the canvas’s getBoundingClientRect() method to obtain the canvas’s bounding box relative to the window. The windowToCanvas() method then returns an object with x and y properties that correspond to the mouse location in the canvas.
Notice that not only does windowToCanvas() subtract the left and top of the canvas’s bounding box from the x and y window coordinates, it also scales those coordinates when the canvas element’s size differs from the size of the drawing surface. See Section 1.1.1, “Canvas Element Size vs. Drawing Surface Size,” on p. 5 for an explanation of canvas element size versus canvas drawing surface size.
The HTML for the application shown in Figure 1.14 is listed in Example 1.5, and the JavaScript is listed in Example 1.6.
Example 1.5. A sprite sheet inspector: HTML
<!DOCTYPE
html
>
<head>
<title>
Sprite sheets</title>
<style>
body{
background:
#dddddd
;}
#canvas
{
position:
absolute
;left:
0px
;top:
20px
;margin:
20px
;background:
#ffffff
;border:
thin inset rgba
(100
,150
,230
,0.5
);cursor:
pointer
;}
#readout
{
margin-top:
10px
;margin-left:
15px
;color:#
blue
;}
</style>
</head>
<body>
<div
id
=
'readout'
></div>
<canvas
id
=
'canvas'
width
=
'500'
height
=
'250'
>
Canvas not supported</canvas>
<script
src
=
'example.js'
></script>
</body>
</html>
Example 1.6. A sprite sheet inspector: JavaScript
var
canvas=
document.
getElementById
(
'canvas'
),
readout=
document.
getElementById
(
'readout'
),
context=
canvas.
getContext
(
'2d'
),
spritesheet=
new
Image
();
// Functions..........................................................function
windowToCanvas
(
canvas,
x,
y)
{
var
bbox=
canvas.
getBoundingClientRect
();
return
{
x:
x-
bbox.
left*
(
canvas.
width/
bbox.
width),
y:
y-
bbox.
top*
(
canvas.
height/
bbox.
height)
}
;
}
function
drawBackground
()
{
var
VERTICAL_LINE_SPACING=
12
,
i=
context.
canvas.
height;
context.
clearRect
(
0
,
0
,
canvas.
width,
canvas.
height);
context.
strokeStyle=
'lightgray'
;
context.
lineWidth=
0.5
;
while
(
i>
VERTICAL_LINE_SPACING*
4
)
{
context.
beginPath
();
context.
moveTo
(
0
,
i);
context.
lineTo
(
context.
canvas.
width,
i);
context.
stroke
();
i-=
VERTICAL_LINE_SPACING;
}
}
function
drawSpritesheet
()
{
context.
drawImage
(
spritesheet,
0
,
0
);
}
function
drawGuidelines
(
x,
y)
{
context.
strokeStyle=
'rgba(0,0,230,0.8)'
;
context.
lineWidth=
0.5
;
drawVerticalLine
(
x);
drawHorizontalLine
(
y);
}
function
updateReadout
(
x,
y)
{
readout.
innerText=
'('
+
x.
toFixed
(
0
)
+
', '
+
y.
toFixed
(
0
)
+
')'
;
}
function
drawHorizontalLine
(
y)
{
context.
beginPath
();
context.
moveTo
(
0
,
y+
0.5
);
context.
lineTo
(
context.
canvas.
width,
y+
0.5
);
context.
stroke
();
}
function
drawVerticalLine
(
x)
{
context.
beginPath
();
context.
moveTo
(
x+
0.5
,
0
);
context.
lineTo
(
x+
0.5
,
context.
canvas.
height);
context.
stroke
();
}
// Event handlers..................................................... canvas.
onmousemove=
function
(
e)
{
var
loc=
windowToCanvas
(
canvas,
e.
clientX,
e.
clientY);
drawBackground
();
drawSpritesheet
();
drawGuidelines
(
loc.
x,
loc.
y);
updateReadout
(
loc.
x,
loc.
y);
}
;
// Initialization..................................................... spritesheet.
src=
'running-sprite-sheet.png'
;
spritesheet.
onload=
function
(
e)
{
drawSpritesheet
();
}
;
drawBackground
();
1.6.2. Keyboard Events
When you press a key in a browser window, the browser generates key events. Those events are targeted at the HTML element that currently has focus. If no element has focus, key events bubble up to the window and document objects.
The canvas element is not a focusable element, and therefore in light of the preceding paragraph, adding key listeners to a canvas is an exercise in futility. Instead, you will add key listeners to either the document or window objects to detect key events.
There are three types of key events:
- keydown
- keypress
- keyup
The keydown and keyup events are low-level events that the browser fires for nearly every keystroke. Note that some keystrokes, such as command sequences, may be swallowed by the browser or the operating system; however, most keystrokes make it through to your keydown and keyup event handlers, including keys such as Alt, Esc, and so on.
When a keydown event generates a printable character, the browser fires a keypress event before the inevitable keyup event. If you hold a key that generates a printable character down for an extended period of time, the browser will fire a sequence of keypress events between the keydown and keyup events.
Implementing key listeners is similar to implementing mouse listeners. You can assign a function to the document or window object’s onkeydown, onkeyup, or onkeypress variables, or you can call addEventListener(), with keydown, keyup, or keypress for the first argument, and a reference to a function for the second argument.
Determining which key was pressed can be complicated, for two reasons. First, there is a huge variety of characters among all the languages of the world. When you must take into consideration the Latin alphabet, Asian ideographic characters, and the many languages of India, just to mention a few, supporting them all is mind boggling.
Second, although browsers and keyboards have been around for a long time, key codes have never been standardized until DOM Level 3, which few browsers currently support. In a word, detecting exactly what key or combination of keys has been pressed is a mess.
However, under most circumstances you can get by with the following two simple strategies:
- For keydown and keyup events, look at the keyCode property of the event object that the browser passes to your event listener. In general, for printable characters, those values will be ASCII codes. Notice the in general caveat, however. Here is a good website that you can consult for interpreting key codes among different browsers: http://bit.ly/o3b1L2. Event objects for key events also contain the following boolean properties:
- altKey
- ctrlKey
- metaKey
- shiftKey
- For keypress events—which browsers fire only for printable characters—you can reliably get that character like this:
var key = String.fromCharCode(event.which);
In general, unless you are implementing a text control in a canvas, you will handle mouse events much more often than you handle key events. One other common use case for key events, however, is handling keystrokes in games. We discuss that topic in Chapter 9.
1.6.3. Touch Events
With the advent of smart phones and tablet computers, the HTML specification has added support for touch events. See Chapter 11 for more information about handling touch events.