Having fun with HTML5 – Canvas, part 4

Following on from part 3 where we basically made a little app that lets you scribble with HTML5’s canvas element, let us push on and see what else we can achieve with the canvas element.

The logical next step would be to made animations, and give you a way to interact with the animation. So how about a little game that lets you shoot moving targets like those classic Point Blank games? Hands up who’s up for that?

image

Good good, that’s decided then! :-)

To Start Off..

First, we’ll need a simple HTML page, something like my previous demos would do:

   1: <div id="wrapper">

   2:     <h1>HTML5 Canvas Demo</h1>

   3:     <h3>Shoot 'em!</h3>

   4:     <aside id="message"></aside>

   5:

   6:     <div class="hide">

   7:         <canvas id="target" width="101" height="101" class="hide"/>

   8:     </div>

   9:

  10:     <div class="block">

  11:         <canvas id="canvas" class="block" width="800" height="700"

  12:                 onSelectStart="this.style.cursor='crosshair'; return false;"/>

  13:     </div>

  14: </div>

Nothing fancy here, the CSS for this page is also pretty basic too, the only thing worth noting is that to get the crosshair cursor inside the canvas I included this in my CSS:

   1: #canvas

   2: {

   3:     cursor: crosshair;

   4: }

The onSelectStart line in the canvas HTML is to ensure that the crosshair cursor is used when you click and hold inside the canvas element.

You might have also noticed that I had specified two canvas elements in the HTML, this is to simplify the task of drawing moving targets onto the main canvas. With the 2D context object’s drawImage function, you can draw an img, canvas, or video element so I can draw the target onto the target canvas during initialization and then reuse it in the main canvas.

Drawing the Target board

Next, I need to draw the target boards, and make them look like this, but without the score on the rings:

image

(Why not just use an image? Because where’s the fun in that! ;-) )

As you can imagine, we can create a target board looking like this by drawing 6 circles, with the innermost 2 circles having roughly half the radius of the other 4 and the outermost 2 circles having the inversed fill/stroke colour combination. Expand the below section to see the initializeTargetCanvas function which takes a canvas element and draws a target board in inside it:

   1: // Draws the target canvas

   2: function initializeTargetCanvas(element) {

   3:     var baseX = 1.5, baseY = 1.5;

   4:

   5:     // get the width and height of the element

   6:     var width = (element.width - baseX * 2) / 2,

   7:         height = (element.height - baseY * 2) / 2;

   8:

   9:     // work out the necessary metrics to draw the target

  10:     var radius = Math.min(width, height),

  11:         centreX = baseX + radius,

  12:         centreY = baseY + radius,

  13:         ringWidth = radius / 10;

  14:

  15:     // get the 2D context to start drawing the target!

  16:     var context = element.getContext("2d");

  17:     context.lineWidth = "2";

  18:

  19:     // define function to draw a ring

  20:     var drawRing = function (strokeStyle, fillStyle, ringRadius) {

  21:         context.strokeStyle = strokeStyle;

  22:         context.fillStyle = fillStyle;

  23:

  24:         // draw the circle

  25:         context.beginPath();

  26:         context.arc(centreX, centreY, ringRadius, 0, Math.PI * 2, true);

  27:         context.closePath();

  28:

  29:         context.stroke();

  30:         context.fill();

  31:     };

  32:

  33:     // draw the rings for each score

  34:     drawRing("#000", "#FFF", radius);

  35:     drawRing("#000", "#FFF", radius -= (ringWidth * 2));

  36:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  37:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  38:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  39:     drawRing("#FFF", "#000", radius -= ringWidth);

  40: }

Animating the Target Boards

Being able to draw an already created target board onto the canvas page is one thing, but how do I animate them using the canvas? Having looked at a few other examples it seems the common way to do animation with the canvas is to simply clear the canvas and redraw the contents on a regular interval. This seems a little low level and requires a bit of plumbing but I’m sure it won’t be long (if not already) before some good, solid frameworks emerge to make these tasks easier.

For now though, let me illustrate how you might create this frame by frame animation yourself.

If you separate the responsibilities of the app, broadly speaking you end up with:

  • a View – i.e. the canvas element, responsible for displaying the targets
  • a Model – the objects representing the targets in the scene, responsible for keeping track of their current position, etc.

If you’ve dealt with any sort of MVC/MVP/MVVM pattern then this kind of separation of duty should be familiar to you already. On each frame (or if you prefer, every time the setInterval delegate function gets invoked), you update the position of the targets in the model, then redraw the targets onto the canvas to reflect their updated positions:

image

This is all you need to do to create a basic animation. So in my case, I need a simple Target object to keep track of:

  • X, Y coordinates of the top left hand corner of the target
  • the radius of the target
  • the direction and speed (per frame) it’s moving at

using MooTools here’s the class I arrived at this Target ‘class’:

   1: /* GLOBAL VARIABLES */

   2: var WIDTH,              // width of the canvas area

   3:     HEIGHT,             // height of the canvas area

   4:

   5:     targets = new Array(),  // the live targets

   6:     targetId = 0,        // the current target ID

   7:

   8:

   9: // define the Target 'class' to represent an on-screen target

  10: var Target = new Class({

  11:     initialize: function (x, y, radius, dx, dy) {

  12:         var _id, _x, _y, _radius, _dx, _dy, is;

  13:

  14:         _id = targetId++;

  15:

  16:         // the X and Y coordinate of the top left corner

  17:         _x = x;

  18:         _y = y;

  19:

  20:         // the radius of the target

  21:         _radius = radius;

  22:

  23:         // the rate of movement in the X and Y direction

  24:         if (dx) {

  25:             _dx = dx;

  26:         } else {

  27:             _dx = Math.ceil(Math.random() * 10);

  28:         }

  29:         if (dy) {

  30:             _dy = dy;

  31:         } else {

  32:             _dy = Math.ceil(Math.random() * 10);

  33:         }

  34:

  35:         // getters

  36:         this.getId = function () {

  37:             return _id;

  38:         }

  39:

  40:         this.getX = function () {

  41:             return _x;

  42:         };

  43:

  44:         this.getY = function () {

  45:             return _y;

  46:         };

  47:

  48:         this.getRadius = function () {

  49:             return _radius;

  50:         };

  51:

  52:         // move the target to its position for the next frame

  53:         this.move = function () {

  54:             _x += _dx;

  55:             _y += _dy;

  56:

  57:             // change direction in X if it 'hits' the border

  58:             if ((_x + _radius * 2) >= WIDTH || _x <= 0) {

  59:                 _dx *= -1;

  60:             }

  61:

  62:             // change direction in Y if it 'hits' the border

  63:             if ((_y + _radius * 2) >= HEIGHT || _y <= 0) {

  64:                 _dy *= -1;

  65:             }

  66:         };

  67:

  68:         // draws the target on the canvas

  69:         this.draw = function () {

  70:             context.drawImage(targetElement, _x, _y);

  71:         };

  72:

  73:         // hit the target!

  74:         this.hit = function () {

  75:             for (var i = 0; i < targets.length; i++) {

  76:                 var target = targets[i];

  77:

  78:                 if (target.getId() == _id) {

  79:                     targets.splice(i, 1);

  80:                     break;

  81:                 }

  82:             }

  83:         };

  84:     }

  85: });

The draw function, which is invoked at regular intervals, first clears the canvas (by filling it with the background colour) then goes through all the targets in the targets array and updates their location and then draw them onto the canvas:

   1: // clear the canvas page

   2: function clear() {

   3:     context.fillStyle = "#000";

   4:     context.fillRect(0, 0, WIDTH, HEIGHT);

   5: }

   6:

   7: // redraw the target boards on the canvas

   8: function draw() {

   9:     // clear the canvas page first

  10:     clear();

  11:

  12:     for (var i = 0; i < targets.length; i++) {

  13:         targets[i].move();

  14:         targets[i].draw();

  15:     }

  16: }

This will give you the basic animation loop, here’s how it looks with 10 moving targets on screen at the same time:

image

Adding Interactions

Now that the animations are in place, let’s add some player interactions. The interactions I’m after here is simple, click in the canvas and knock out any (can be one or more) target that’s clicked on.

I’ve already gone over the process of calculating the coordinates of the click in respect to the canvas in a previous blog post here, this is what that code looks like:

   1: // works out the X, Y position of the click INSIDE the canvas from the X, Y 

   2: // position on the page

   3: function getPosition(mouseEvent, element) {

   4:     var x, y;

   5:     if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) {

   6:         x = mouseEvent.pageX;

   7:         y = mouseEvent.pageY;

   8:     } else {

   9:         x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;

  10:         y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;

  11:     }

  12:

  13:     return { X: x - element.offsetLeft, Y: y - element.offsetTop };

  14: }

If I can work out where you’ve clicked in the canvas’ coordinate system, then I can simply run a hit test against each moving target and compare the distance between the click and the centre of the target and the target’s radius:

image

If the distance is smaller or equal to the radius then the click happened INSIDE the target and therefore it’s a hit, otherwise it’s a miss. Sounds reasonable enough? Here’s the code that takes a position (a simple object with X and Y position of the click inside the canvas’ coordinate system) and return the targets it has hit:

   1: // check if the player managed to hit any of the live targets

   2: function hitTest(position) {

   3:     var hitTargets = new Array();

   4:

   5:     // check if the position is within the bounds of any of the live targets

   6:     for (var i = 0; i < targets.length; i++) {

   7:         var target = targets[i];

   8:

   9:         var targetCentreX = target.getX() + target.getRadius(),

  10:             targetCentreY = target.getY() + target.getRadius();

  11:

  12:         // work out the distance between the position and the target's centre

  13:         var xdiff = position.X - targetCentreX,

  14:             ydiff = position.Y - targetCentreY,

  15:             dist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(ydiff, 2));

  16:

  17:         // if that distance is less than the radius of the target then the

  18:         // position is inside the target

  19:         if (dist <= target.getRadius()) {

  20:             hitTargets.push(target);

  21:         }

  22:     }

  23:

  24:     return hitTargets;

  25: }

To hook this up, I added an event handler to the mousedown event on the canvas during initialization:

   1: $("#canvas").mousedown(function (mouseEvent) {

   2:     // get the coordinates of the click inside the canvas

   3:     var position = getPosition(mouseEvent, this);

   4:

   5:     // find out which targets were hit

   6:     var hitTargets = hitTest(position);

   7:

   8:     // hit the targets

   9:     for (var i = 0; i < hitTargets.length; i++) {

  10:         hitTargets[i].hit();

  11:     }

  12: }

And now, when you ‘hit’ a target it’ll be removed from the array of moving targets and therefore won’t be drawn again when the canvas is refreshed in the next frame.

Adding Notifications

Finally, for this first-pass implementation of a mini-shooting game, I’d like to add some notifications to tell you when you’ve hit something, or when you’ve missed completely!

This is slightly trickier than the targets as the messages should not stay around forever until some user-triggered action, instead it should be shown for a given amount of time (or frames). To facilitate this requirement, I created another ‘class’ called Message:

   1: // define the Message 'class' to represent an on-screen message

   2: var Message = new Class({

   3:     initialize: function (x, y, message, duration) {

   4:         var _id, _x, _y, _message, _duration;

   5:

   6:         _id = messageId++;

   7:

   8:         // X, Y coordinates of where to display the message

   9:         _x = x;

  10:         _y = y;

  11:

  12:         // the message

  13:         _message = message;

  14:

  15:         // how many frames to display the message for

  16:         _duration = duration;

  17:

  18:         this.getId = function () {

  19:             return _id;

  20:         }

  21:

  22:         this.draw = function () {

  23:             if (_duration >= 0) {

  24:                 context.textBaseline = "middle";

  25:                 context.textAlign = "center";

  26:                 context.fillStyle = "#FFF";

  27:                 context.strokeStyle = "#000";

  28:                 context.font = "bold 40px arial";

  29:

  30:                 // draw the message at the specified X, Y coordinates

  31:                 context.fillText(_message, _x, _y);

  32:

  33:                 _duration--;

  34:             } else {

  35:                 // remove the message

  36:                 for (var i = 0; i < messages.length; i++) {

  37:                     var message = messages[i];

  38:

  39:                     if (message.getId() == _id) {

  40:                         messages.splice(i, 1);

  41:                         break;

  42:                     }

  43:                 }

  44:             }

  45:         }

  46:     }

  47: });

The Message objects can only been drawn a number of times, after which it will remove itself from the array of messages currently being displayed.

To make it a bit more interesting, I defined a number of messages which will be displayed depending on how many targets you’ve managed to hit at once:

   1: // define the messages to show

   2: var hitMessages = new Array();

   3: hitMessages[0] = "MISS";

   4: hitMessages[1] = "HIT!";

   5: hitMessages[2] = "DOUBLE HIT!!";

   6: hitMessages[3] = "HAT-TRICK!!!";

   7: hitMessages[4] = "UN~BELIEVABLE!!!!";

   8: hitMessages[5] = "OH MY GOSH!!";

On the mousedown event handler (see above) I added these couple of lines to push a new message to the messages stack to be displayed for 30 frames, and the message is determined by how many targets was hit:

   1: // use one of the defined messages if possible, otherwise use a default

   2: var hitMessage = hitMessages[hitTargets.length];

   3: if (hitMessage == undefined)

   4: {

   5:     hitMessage = "TOO GOOOOOOOOD..";

   6: }

   7:

   8: messages.push(new Message(position.X, position.Y, hitMessage, 30));

For example, if you managed to hit three targets one shot:

image

Pretty neat, eh? :-)

Demo

Here is the full demo, over the next couple of days, I will polish it up and add some more features to make it feel more gamey and post another update. In the mean time though, feel free to play around with it and let me know of any suggestions you have on how to improve it!

References:

HTML5 canvas cheat sheet

Dive into HTML5 – peeks, pokes and pointers

Related posts:

Having fun with HTML5 – Canvas, part 1

Having fun with HTML5 – Canvas, part 2

Having fun with HTML5 – Canvas, part 3

Having fun with HTML5 – Canvas, part 5

Having fun with HTML5 – Canvas, part 3

Following on from part 2 where I wrote a simple page with a canvas area where you can scribble, I thought I’d add a couple of new features:

  • ability to show the image as PNG image so user can save it
  • change the line cap
  • change the line join
  • change the shadow settings (colour, offset, blur)

Show PNG

The <canvas> element defines a rather useful toDataURL() method:

“The toDataURL() method must, when called with no arguments, return a data: URL containing a representation of the image as a PNG file.”

To get the data in PNG format and get the browser to render it for the user:

// display the image
$("#btnShowPng").click(function () {
    window.location = element.toDataURL("image/png");
});

Couldn’t be simpler, right?!

Changing the lineCap property

The 2D context defines a lineCap property which you can set to butt, round or square, it determines the ending style of the line (default is butt):

image

So here’s a simple set of radio buttons:

<article>
    Line cap:
    <input type="radio" name="linecap" value="butt" checked="checked"/>Butt
    <input type="radio" name="linecap" value="round"/>Round
    <input type="radio" name="linecap" value="square"/>Square
</article>

image

and an event handler for their change events:

// change the linecap when the "linecap" radio button changes
$("input[name=linecap]:radio").change(function (event) {
    context.lineCap = event.target.value;
});

Changing the lineJoin property

In addition to the lineCap property, the 2D context also defines a lineJoin property which can be set to miter, round or bevel, it determines how the corners look (default is miter):

image

Here’s the set of radio buttons:

<article>
    Line join:
    <input type="radio" name="linejoin" value="miter" checked="checked"/>Miter
    <input type="radio" name="linejoin" value="round"/>Round
    <input type="radio" name="linejoin" value="bevel"/>Bevel
</article>

image

and its corresponding event handler:

// change the linecap when the "linejoin" radio button changes
$("input[name=linejoin]:radio").change(function (event) {
    context.lineJoin = event.target.value;
});

Changing the Shadow properties

The 2D context has a couple of shadow related properties:

  • shadowColor
  • shadowOffsetX
  • shadowOffsetY
  • shadowBlur

The shadowColor property can take in a colour using the rgba function (default value is “rgba(0, 0, 0, 0)”, i.e. a fully transparent black), whilst the other properties require a numeric value greater than or equal to 0.

Here’s the HTML marker for configuring the shadow properties:

<article>
    <section id="shadowColorSection">
        Shadow Colour:
        R
        <input type="text" id="txtColourR" value="0" class="narrow" />
        G
        <input type="text" id="txtColourG" value="0" class="narrow" />
        B
        <input type="text" id="txtColourB" value="0" class="narrow" />
        A
        <input type="text" id="txtColourA" value="0" class="narrow" />
    </section>

    <section>
        Shadow Offset:
        X
        <input type="text" id="txtOffsetX" value="0" class="narrow" />
        Y
        <input type="text" id="txtOffsetY" value="0" class="narrow" />
    </section>

    <section>
        Shadow Blur:
        <input type="text" id="txtBlur" value="0" class="narrow" />
    </section>
</article>

image

and the javascript to go along with them:

$("#shadowColorSection > input:text").change(function (event) {
    setShadowColour(context);
});

$("#txtOffsetX").change(function (event) {
    context.shadowOffsetX = event.target.value;
});

$("#txtOffsetY").change(function (event) {
    context.shadowOffsetY = event.target.value;
});

$("#txtBlur").change(function (event) {
    context.shadowBlur = event.target.value;
});

…

// set the shadow colour from the RBGA text boxes
function setShadowColour(context) {
    var r = $("#txtColourR").val(),
        g = $("#txtColourG").val(),
        b = $("#txtColourB").val(),
        a = $("#txtColourA").val();

    context.shadowColor = "rgba(" + r + "," + g + "," + b + "," + a + ")";
}

Updating the Clear button handler

Now that we can modify a few more of 2D context’s properties, it’d be nice if the changes are remembered after we clear the canvas, so the Clear button’s event handler needs to be modified too to keep track of the current values and reapplying them:

// clear the content of the canvas by resizing the element
$("#btnClear").click(function () {
    // remember the current line width
    var currentWidth = context.lineWidth,
        currentLineCap = context.lineCap,
        currentLineJoin = context.lineJoin;
    var currentShadowColour = context.shadowColor,
        currentShadowOffsetX = context.shadowOffsetX,
        currentShadowOffsetY = context.shadowOffsetY,
        currentShadowBlur = context.shadowBlur;

    // set the element's width to erase canvas content
    element.width = element.width;

    context.lineWidth = currentWidth;
    context.lineCap = currentLineCap;
    context.lineJoin = currentLineJoin;

    context.shadowColor = currentShadowColour;
    context.shadowOffsetX = currentShadowOffsetX;
    context.shadowOffsetY = currentShadowOffsetY;
    context.shadowBlur = currentShadowBlur;
});

Demo

The demo can be found here.

Related posts:

Having fun with HTML5 – Canvas, part 1

Having fun with HTML5 – Canvas, part 2

Having fun with HTML5 – Canvas, part 4

Having fun with HTML5 – Canvas, part 5

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.

image

HTML

The HTML for the page is simple enough, the key thing is obviously the canvas 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 canvas element is as below:

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

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:

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

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:

$(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();
    }
});

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:

// 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 references to both the canvas element and the 2D drawing context 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 coordinates of the click inside the canvas 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 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:

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

Having fun with HTML5 – Canvas, part 4

Having fun with HTML5 – Canvas, part 5

Having fun with HTML5 – Canvas, part 1

One of the cool new features introduced by HTML5 is the new <canvas> tag, which defines an area for you to draw graphics on using javascript.

Basics

To create a canvas element is as easy as inserting a <canvas> tag like this:

<canvas id="myCanvas" width="400" height="400"></canvas>

Typically you will give it an ID as you will need to look it up with javascript later in order to do anything with it, and as for the javascript, pretty much all javascript that interacts with the canvas need to start with something along the line of:

// get the element by ID
var element = document.getElementById("myCanvas");

// get the 2D context which is what you use to draw
var context = element.getContext("2d");

To make this more robust you’re likely to need more plumbing code to check whether the browser supports the <canvas> tag and if element is not null and that the getContext method exists, etc. The easiest way to check for browser support for the <canvas> tag would be to use a simple library like Modernizr, and all you need is to check the .canvas property:

<script type="text/javascript" src="modernizr/modernizr-1.6.min.js"></script>
…
$(document).ready(function () {
    // check if canvas is not supported
    if (!Modernizr.canvas) {
        // fall back logic here
    } else {
        // normal steps here
    }
});

Once you’ve got the 2D context (specification for a 3D context is in the pipeline and some vendors have released their own 3D context API) you can use it to draw paths, shapes, text, etc. etc.

API

Seeing as all the action around a canvas element is centred around the 2D context, it’s only fitting that it gets its own specification. The full specification (work in progress of course) of the 2D context object can be found here.

Have a look at the links in the References section for some useful articles which go through the various API calls you’ll likely need to use in detail.

Resetting a canvas

Setting the width or height of the canvas element will erase its contents and reset all the properties of its drawing context to their default, so something like this will suffice:

var canvas = $("#myCanvas");
canvas.width = canvas.width;

Demo

Once you’ve had a chance to look at the basic API methods, I’ve put together a quick example here to demonstrate how to draw some basic gradients using two stop and how to draw a chessboard using the fillRec and strokeRect methods:

// draws a chessboard
function drawChessboard() {
// define the constants
var baseX = 0.5, baseY = 0.5, width = 50;
// get the 2D context from the “chessboard” canvas
var context = document.getElementById(“chessboardCanvas”).getContext(“2d”);

// draws the 8 by 8 chessboard
for (var i = 0; i < 8; i++) { for (var j = 0; j < 8; j++) { var x = baseX + width * i, y = baseY + width * j; // draw the rectangle context.strokeRect(x, y, width, width); // fill the odd number rectangles if ((i + j) % 2 != 0) { context.fillRect(x, y, width, width); } } } } [/code] The baseX and baseY are set to 0.5 for the reason explained in the Dive into HTML5 article (see reference), that if you draw a one pixel wide line between whole number coordinates you will end up with a two pixels wide line instead.

The rest is fairly straight forward and draws a 8×8 chessboard of alternating empty and filled squares, left to right, top to bottom, with the first square being unfilled.

References:

Dive into HTML5 – Canvas

Mozilla Developer Center’s canvas tutorial

Related posts:

Having fun with HTML5 – Canvas, part 2

Having fun with HTML5 – Canvas, part 3

Having fun with HTML5 – Canvas, part 4

Having fun with HTML5 – Canvas, part 5

Having fun with HTML5 – contenteditable attribute

As I go through the HTML5 spec, I find some useful new feature just about everywhere, and the new contenteditable attribute is certainly one of those. It’s supported by most elements and provides a simple and yet effective way for you to allow the users to edit user contents (blog posts for instance) inline as opposed to having to open up a separate form.

It couldn’t be simpler to use it, just set the contenteditable attribute of your element to true:

<article id="editable" contenteditable="true">
    <p>This area is an article, its border will light up when you click anywhere inside.</p>
    …
</article>

And now when the user can edit the content of the element themselves:

image image

Here is a quick demo I’ve put together which uses two buttons to save and clear changes to/from the local storage, though you can just easily save the changes automatically when the element loses the focus.