Fun with the HTML 5 Canvas Element

July 3, 2011

HTML 5 is out, and pretty much all browsers support it (you have to install a bit of third-party software for IE support, but you can make it work). This represents a big change in what we can do with plain-old HTML - the W3C has done an amazing job of looking at all of the features that web designers have hacked on top of core HTML and made them part of the standard feature set that the browser must support to be compliant.

The new feature I'm most excited about is the HTML 5 Canvas object. The canvas element just declares a drawing area for your scripts to render images onto - this makes HTML/Javascript a truly rich GUI-capable platform for the first time. Anything you could do with, say, a Java AWT Graphics object, you can now do with an HTML 5 canvas.

Figure 1 is a very simple demo - you can pick up any of the colored squares and move them around the canvas:

Sorry, but your browser doesn't recognize the canvas tag.

Figure 1: Drag and Drop Demonstration

The declaration:

<canvas id="dragdrop" width="300" height="300">
Creates a canvas - a drawing area for javascript (or, technically, any other scripting language) to draw shapes into. By itself, the canvas is pretty unintersting; it's up to the script to get the drawing context out of the canvas and draw shapes onto it:
  var canvas = document.getElementById( 'dragdrop' );
  var ctx = canvas.getContext( '2d' );
	ctx.fillStyle = "rgb(255,0,0)";
	ctx.fillRect( 0, 0, 50, 50 );
This finds the canvas element, extracts its 2d "context" object, and draws onto the context. Note that the canvas element itself is not involved in the drawing process beyond providing a drawing context - all further drawing operations are done on the context.

The fillStyle sets the color (red), and fillRect draws a rectangle using the active fillStyle at coordinate 0, 0 (the upper left corner of the canvas) of width 50 and height 50.

To make this a tad more interesting, declare an array of colored rectangles:

var r = [
  { x: 0, y: 0, w:50, h:50, r: 255, g: 0, b: 0 },
  { x: 50, y: 0, w:50, h:50, r: 0, g: 255, b: 0 },
  { x: 100, y: 0, w:50, h:50, r: 0, g: 0, b: 255 },
  { x: 150, y: 0, w:50, h:50, r: 0, g: 255, b: 255 },
  { x: 200, y: 0, w:50, h:50, r: 255, g: 0, b: 255 },
  { x: 250, y: 0, w:50, h:50, r: 255, g: 255, b: 0 },
];
And draw them in a loop:
  for ( var i in r )
  {
    ctx.fillStyle = "rgb(" + r[ i ].r + "," + r[ i ].g + "," + r[ i ].b + ")";
    ctx.fillRect( r[ i ].x, r[ i ].y, r[ i ].w, r[ i ].h );
  }
Notice how I built the "rgb" string dynamically from object values. This LISP-like self-referentiality is one of the most powerful features of Javascript - and because of its power, the one you're most likely to shoot yourself in the foot with.

This is a little more fun to look at, but still not interactive. To let the user interact with the canvas, go ahead and add mouse handler events to the canvas object:

<canvas type="textarea" id="canvas" width="300" height="300"
	onmousedown="handleMouseDown( event )"
	onmousemove="handleMouseMove( event )"
  onmouseup="handleMouseUp( event )">

The only challenge now is figuring out which rectangle the user clicked. The MouseEvent object returned to onmousexxx handlers provides quite a bit of information about the click, none of it is relative to the actual object that was clicked.

A non-standard "offsetX" is provided by every browser except for Firefox; to make this work cross-browsers, you have to do this:

    var x = event.offsetX?(event.offsetX):event.pageX-document.getElementById('dragdrop').offsetLeft;
    var y = event.offsetY?(event.offsetY):event.pageY-document.getElementById('dragdrop').offsetTop;

To make the whole thing animate, add a "dragging" object that is set when the user clicks a rectangle and on each captured mousemove, redraw the canvas if the user is dragging one of the rectangles:

var dragging = null;

function handleMouseMove(event)
{
  if ( dragging != null )
  {
    var x = event.offsetX?(event.offsetX):event.pageX-document.getElementById('canvas').offsetLeft;
    var y = event.offsetY?(event.offsetY):event.pageY-document.getElementById('canvas').offsetTop;
    var canvas = document.getElementById( 'canvas' );
    var ctx = canvas.getContext( '2d' );

    ctx.clearRect( dragging.x, dragging.y, dragging.w, dragging.h );
    dragging.x = x;
    dragging.y = y;

    drawRectangles();
  }
}

Figure 2 is another sample - the famous "bouncing ball" demo that used to be the start of any book about graphics programming. Click the canvas to start the animation; click again to make it stop.

Sorry, your browser doesn't support the canvas tag

Figure 2: Bouncing Ball Demonstration

A couple of interesting points about this script. The first is how it handles animation - the DOM setTimeout function is called to have the animation script repeat itself every 10 ms:

	bounceAnimation = setTimeout( "animateBounce()", 10 );

Second, drawing a circle is a bit more involved than drawing a rectangle - you won't find a "fillCircle" or "drawCircle" in the official 2D canvas specification. Instead, you have to create a path and fill it:

	ctx.fillStyle = "rgb( 0, 0, 0 )";
	ctx.beginPath();
	ctx.arc( bounce_ball.x, bounce_ball.y, bounce_ball.r, 0, Math.PI * 2, false );
	ctx.fill();
Although this seems like a long way to go to simply draw a circle, it's the core of the flexibility of the HTML 5 canvas. You can define any arbitrary path — you can include lines, arcs, and bezier curves — and fill it when you're done, or "stroke" it to draw just the outline.

Note, however, that there's no ".clear()" function that works on a path. If you want to clear an area, you either have to draw it in the background color, or use "clearRect" as I've done here:

	ctx.clearRect( bounce_ball.x - bounce_ball.r, bounce_ball.y - bounce_ball.r,
		bounce_ball.r * 2, bounce_ball.r * 2 );

You can actually turn this into a fun game, shown in figure 3. Click the canvas to start the demo; use the arrow keys to move the player right and left. Press the space bar to release the ball; if you can get it to break all of the blocks, you win. If the ball falls below your player, you lose a life - you get three tries. I've been trying this all darned day and I haven't been able to beat it.

Sorry, your browser doesn't support the canvas tag.

Figure 3: Block Breaker Game

Just do a "view source" if you want to see what I added to get the bouncing ball demo from figure 2 to turn into the interactive game in figure 3. I did a couple of things in there like using global variables and hard-coding constants that I wouldn't recommend in general; but the point here is to have a little fun with the HTML 5 canvas, if not following good software engineering principals.

There's not much new here from the perspective of the HTML 5 canvas, but I think it's sort of a neat little demonstration of what you can accomplish with this new construct. One thing worth pointing out, though. I have two similar demonstrations on this page — the bouncing ball demo in figure 2 and the brick breaker demo in figure 3. However, I have to be careful on this page to ensure that their function names were unique, or there would be a namespace clash on this page. It's common, in Javascript programming, to wrap the entire set of functions inside a "var" declaration like this:

var namespace = function() {
	// Rest of the code goes here
}();
Although there's nothing wrong with this if you understand what it's for, it's confusing when you first see it, and the proliferation of such scripts indicates that Javascript really ought to standardize on a common namespace keyword.

I've only scratched the tip of the iceberg here with the HTML 5 canvas; there's a comprehensive set of tutorials available that covers just about everything you could possibly want to know. If you found this article interesting, you should definitely check out their tutorials.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
Tori, 2011-08-09
Good to see a tlaent at work. I can't match that.
Andrew, 2012-11-12
Hi Folks, I am playing around with Figure 3: Block Breaker Game and cant find a way to speed up the process. The ball just doesnt want to speed up! I have tried animation = setTimeout( "animate()", 1 );} and animation = setTimeout( "animate()", 0 );} but it doesnt seem to have any effect. Any ideas?
Josh, 2012-11-14
Hi, Andrew - the most direct way to speed up the ball is to change the dx value in "handleKeyDown" - that will cause it to move more pixels in each frame. Reducing the animation timeout will speed things up (try setting it to 100 to verify), but since the timeout is already set to 7 ms, you won't perceive any real change going down to 1.
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts