Having fun with HTML5 — Canvas, part 2
Earlier I explored some of the basic drawing methods available on the 2D context of the new canvas element in HTML5, moving on from there, I’ve put together another quick demo here (see image below) which lets the user scribble inside the canvas element.
HTML
The HTML for the page is simple enough, the key thing is obviously the canvas element:
[code lang=”javascript” htmlscript=”true” collapse=”true”]
HTML5 Demo
Drawing tools:
Line width:
pixels
Canvas area:
onSelectStart=”this.style.cursor=’crosshair’; return false;”>
[/code]
CSS
The css for the canvas element is as below:
[code lang=”css”]
#drawingCanvas
{
border-style: dotted;
border-width: 1px;
border-color: Black;
cursor: crosshair;
}
[/code]
I want to use the ‘crosshair’ cursor whenever you’re in the canvas area, but the above css only works when the left mouse button is not held down (when by default, the cursor changes to the ‘text’ cursor) so in addition to the css I added an event handler to change the cursor on the select start event in the HTML mark up:
[code lang=”javascript” htmlscript=”true”]
onSelectStart=”this.style.cursor=’crosshair’; return false;”>
[/code]
Javascript
Canvas support detection
When the page finishes loading I first check if the browser supports the canvas element, again using the Modernizr library, and display a warning message if canvas is not supported:
[code lang=”javascript” collapse=”true”]
$(document).ready(function () {
// display a warning message if canvas is not supported
if (!Modernizr.canvas) {
$(“#message”).html(“
WARNING: Your browser does not support HTML5’s canvas feature, you won’t be able to see the drawings below
“);
$(“article”).hide();
} else {
initialize();
}
});
[/code]
Designing the interactions
The interaction I’m after here is such that you start drawing by clicking and holding down the left mouse button and the path is drawn as you move the mouse while still holding the mouse button, and you stop drawing by releasing the mouse button or moving the mouse out of the canvas area. So ultimately it all boils down to the standard onmousedown, onmousemove, onmouseup and onmouseout events.
There’s one more problem which we have to solve first though — how to resolve the x, y coordinates of the click in relation to the canvas (rather than the page itself). The problem is that mouse events are implemented differently in different browsers, so here’s a helper function which encapsulates that particular piece of logic:
[code lang=”javascript” collapse=”true”]
// works out the X, Y coordinates of the click INSIDE the canvas element from the X, Y
// coordinates on the page
function getPosition(mouseEvent, element) {
var x, y;
if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) {
x = mouseEvent.pageX;
y = mouseEvent.pageY;
} else {
x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return { X: x — element.offsetLeft, Y: y — element.offsetTop };
}
[/code]
Mouse event handlers
First, we need to obtain references to both the canvas element and the 2D drawing context so we can use them later:
[code lang=”javascript”]
var element = document.getElementById(“drawingCanvas”);
var context = element.getContext(“2d”);
[/code]
For the mouse down event, we need to first work out the x and y coordinates of the click inside the canvas then start our path at that point:
[code lang=”javascript” collapse=”true”]
// start drawing when the mousedown event fires, and attach handlers to
// draw a line to wherever the mouse moves to
$(“#drawingCanvas”).mousedown(function (mouseEvent) {
var position = getPosition(mouseEvent, element);
context.moveTo(position.X, position.Y);
context.beginPath();
// attach event handlers
$(this).mousemove(function (mouseEvent) {
drawLine(mouseEvent, element, context);
}).mouseup(function (mouseEvent) {
finishDrawing(mouseEvent, element, context);
}).mouseout(function (mouseEvent) {
finishDrawing(mouseEvent, element, context);
});
});
// draws a line to the x and y coordinates of the mouse event inside
// the specified element using the specified context
function drawLine(mouseEvent, element, context) {
var position = getPosition(event, element);
context.lineTo(position.X, position.Y);
context.stroke();
}
// draws a line from the last coordiantes in the path to the finishing
// coordinates and unbind any event handlers which need to be preceded
// by the mouse down event
function finishDrawing(mouseEvent, element, context) {
// draw the line to the finishing coordinates
drawLine(mouseEvent, element, context);
context.closePath();
// unbind any events which could draw
$(element).unbind(“mousemove”).unbind(“mouseup”).unbind(“mouseout”);
}
[/code]
The approach I have taken here is to attach the event handlers for mouse move/up/out inside the mouse down event handler (which marks the start of the drawing process), these handlers are then unbound when mouse up or mouse out event occurs (which marks the end of the drawing process).
This approach removes the need for a global flag (don’t you just hate those by now!?) to track when the code needs to draw a line. It also makes sure that the logical start and end of the ‘drawing’ event (which as stated before, starts at mouse down and finishes at either mouse up or mouse out) matches the lifetime of the handlers required to handle this so that we don’t handle the mouse move/up/out events unless we need to.
To round things off, there are two more handlers needed to clear the canvas and to change the width of the lines being drawn:
[code lang=”javascript” collapse=”true”]
// clear the content of the canvas by resizing the element
$(“#btnClear”).click(function () {
// remember the current line width
var currentWidth = context.lineWidth;
element.width = element.width;
context.lineWidth = currentWidth;
});
// change the line width
$(“#btnLinewidth”).change(function (event) {
if (!isNaN(event.target.value)) {
context.lineWidth = event.target.value;
}
});
[/code]
Oh and one more thing, you might have noticed that I’ve chained some of the method calls, such as this:
[code lang=”javascript”]
$(element).unbind(“mousemove”).unbind(“mouseup”).unbind(“mouseout”);
[/code]
This is a simple optimization step to reduce the number of times jQuery needs to look through the DOM to identify matching elements. So that’s it, a simple HTML5 page to let you scribble in a canvas page :-)
Related posts:
Having fun with HTML5 — Canvas, part 1
Having fun with HTML5 — Canvas, part 3