Ear­lier I explored some of the basic draw­ing meth­ods avail­able on the 2D con­text of the new can­vas ele­ment in HTML5, mov­ing on from there, I’ve put together another quick demo here (see image below) which lets the user scrib­ble inside the can­vas element.

image

HTML

The HTML for the page is sim­ple enough, the key thing is obvi­ously the can­vas element:

<section id="wrapper">
    <h1>HTML5 Demo</h1>
    <aside id="message"></aside>

    <article>
        <section class="block">
            <h4>Drawing tools:</h4>
            <article>
                Line width:
                <input type="text" id="btnLinewidth" value="1"></input> pixels
            </article>
            <article>
                <input type="submit" id="btnClear" value="Clear"></input>
            </article>
        </section>
        <section class="block">
            <h4>Canvas area:</h4>
            <canvas id="drawingCanvas" width="400" height="400" class="block"
                    onSelectStart="this.style.cursor='crosshair'; return false;"></canvas>
        </section>
    </article>
</section>

CSS

The css for the can­vas ele­ment is as below:

#drawingCanvas
{
    border-style: dotted;
    border-width: 1px;
    border-color: Black;
    cursor: crosshair;
}

I want to use the ‘crosshair’ cur­sor when­ever you’re in the can­vas area, but the above css only works when the left mouse but­ton is not held down (when by default, the cur­sor changes to the ‘text’ cur­sor) so in addi­tion to the css I added an event han­dler to change the cur­sor on the select start event in the HTML mark up:

<canvas id="drawingCanvas" width="400" height="400" class="block"
        onSelectStart="this.style.cursor='crosshair'; return false;"></canvas>

Javascript

Can­vas sup­port detection

When the page fin­ishes load­ing I first check if the browser sup­ports the can­vas ele­ment, again using the Mod­ern­izr library, and dis­play a warn­ing mes­sage if can­vas is not supported:

$(document).ready(function () {
    // display a warning message if canvas is not supported
    if (!Modernizr.canvas) {
        $("#message").html("<p><b>WARNING</b>: Your browser does not support HTML5's canvas feature, you won't be able to see the drawings below</p>");
        $("article").hide();
    } else {
        initialize();
    }
});

Design­ing the interactions

The inter­ac­tion I’m after here is such that you start draw­ing by click­ing and hold­ing down the left mouse but­ton and the path is drawn as you move the mouse while still hold­ing the mouse but­ton, and you stop draw­ing by releas­ing the mouse but­ton or mov­ing the mouse out of the can­vas area. So ulti­mately it all boils down to the stan­dard onmouse­down, onmouse­move, onmouseup and onmouse­out events.

There’s one more prob­lem which we have to solve first though – how to resolve the x, y coor­di­nates of the click in rela­tion to the can­vas (rather than the page itself). The prob­lem is that mouse events are imple­mented dif­fer­ently in dif­fer­ent browsers, so here’s a helper func­tion which encap­su­lates that par­tic­u­lar piece of logic:

// 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 };
}

Mouse event handlers

First, we need to obtain ref­er­ences to both the can­vas ele­ment and the 2D draw­ing con­text so we can use them later:

var element = document.getElementById("drawingCanvas");
var context = element.getContext("2d");

For the mouse down event, we need to first work out the x and y coor­di­nates of the click inside the can­vas then start our path at that point:

// 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");
}

The approach I have taken here is to attach the event han­dlers for mouse move/up/out inside the mouse down event han­dler (which marks the start of the draw­ing process), these han­dlers are then unbound when mouse up or mouse out event occurs (which marks the end of the draw­ing 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 log­i­cal start and end of the ‘draw­ing’ event (which as stated before, starts at mouse down and fin­ishes at either mouse up or mouse out) matches the life­time of the han­dlers required to han­dle this so that we don’t han­dle the mouse move/up/out events unless we need to.

To round things off, there are two more han­dlers needed to clear the can­vas and to change the width of the lines being drawn:

// 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;
    }
});

Oh and one more thing, you might have noticed that I’ve chained some of the method calls, such as this:

$(element).unbind("mousemove").unbind("mouseup").unbind("mouseout");

This is a sim­ple opti­miza­tion step to reduce the num­ber of times jQuery needs to look through the DOM to iden­tify match­ing ele­ments. So that’s it, a sim­ple HTML5 page to let you scrib­ble in a can­vas page :-)

Related posts:

Hav­ing fun with HTML5 — Can­vas, part 1

Hav­ing fun with HTML5 — Can­vas, part 3

Hav­ing fun with HTML5 — Can­vas, part 4

Hav­ing fun with HTML5 — Can­vas, part 5

Share

3 Responses to “Having fun with HTML5 — Canvas, part 2”

  1. […] on from part 2 where I wrote a sim­ple page with a can­vas area where you can scrib­ble, I thought I’d add a […]

  2. […] Hav­ing fun with HTML5 — Can­vas, part 2 […]

  3. […] the coor­di­nates of the click in respect to the can­vas in a pre­vi­ous blog post here, this is what that code […]

Leave a Reply