Getting Started with the Canvas Element and EaselJS
One of the features of HTML5 that developers are most excited about is the Canvas element. The Canvas element essentials provides a bitmap canvas for dynamically rendering shapes and bitmap graphics. It is very similar to the Flash Player’s Bitmap and BitmapData classes.
However, working with the Canvas element can be difficult, especially if you need to manage, update and or / animate multiple shapes and bitmaps. Unlike the Flash Player, the Canvas element does not have a concept of a display list or individual items to render. Instead, it provides a single Canvas on which to draw, and it is up to the developer to determine what needs to be rendered and when.
Grant Skinner has release a JavaScript library named EaselJS, which attempts to provide a Flash like DisplayList API in order to make it easier to work with the Canvas element. The library is just an alpha release, but is surprisingly full featured at this early stage. If you are interested in experimenting with the Canvas API, this is a great way to get started.
In this post, I will show how to get started animating content in the Canvas element using the EaselJS JavaScript library.
Here is a list of the main classes in the library:
- DisplayObject : Abstract base class for all display elements in EaselJS. Exposes all of the display properties (ex. x, y, rotation, scaleX, scaleY, alpha, shadow, etc) that are common to all display objects.
- Stage : The root level display container for display elements that wraps the Canvas element.
- Container : A nestable display container, which lets you aggregate display objects and manipulate them as a group.
- Text : Renders text in the context of the display list.
- Bitmap : Draws an image, video or canvas to the canvas according to its display properties.
- BitmapSequence : Displays animated or dynamic sprite sheets (images with multiple frames on a grid), and provides APIs for managing playback and sequencing.
- Graphics : Provides a simple but powerful API for dynamically drawing vector graphics.
- Shape : Renders vector art via the Graphics object within the context of the display list.
You can view the complete api docs here.
Now, before we get started, lets look at where you can actually use the Canvas element (and thus EaselJS). Canvas is part of the HTML5 specification, and is supported in the latest version of most current browsers, including:
- Safari
- Google Chrome
- Opera
- Firefox
However, there is one exception, and it is a pretty big one. Internet Explorer does not currently support the Canvas element (although the next version of the Internet Explorer will). According to NetMarketShare, Internet Explorer 6, 7 and 8 make up about 57% of the browser market, which is a large segment of users. There is a project named ExplorerCanvas that attempts to replicate Canvas support in Internet Explorer, but EaselJS has not been tested with it yet. Keep these points in mind when considering using the Canvas element.
Now that we have a good idea of where and when we can use the Canvas element, lets look at a simple example. In this example, we will use EaselJS to dynamically draw a circle and animate it across the Canvas. This will show us how to set up the library, introduce us to some basic concepts when working with the library, and show how to animate a graphic.
First, here is the working example:
Now, lets look at the code, with comments:
<!DOCTYPE html> <html lang="en"><head> <meta charset="utf-8" /> <meta name="author" content="Mike Chambers" /> <meta name="keywords" content="" /> <meta name="description" content="" /> <meta name="copyright" content="Mike Chambers" /> <meta name="robots" content="index,follow" /> <title>TITLE</title> <style> #stageCanvas { background-color:#333333; } </style> <!-- import the Easel library. Downloaded from: http://easeljs.com/ --> <script src="scripts/easel.js"></script> <script> //EaselJS Stage instance that wraps the Canvas element var stage; //EaselJS Shape instance that we will animate var circle; //radius of the circle Graphics that we will draw. var CIRCLE_RADIUS = 10; //x position that we will reset Shape to when it goes off //screen var circleXReset; //EaselJS Rectangle instance we will use to store the bounds //of the Canvas var bounds; //initialize function, called when page loads. function init() { //check and see if the canvas element is supported in //the current browser //http://diveintohtml5.org/detect.html#canvas if(!(!!document.createElement('canvas').getContext)) { var wrapper = document.getElementById("canvasWrapper"); wrapper.innerHTML = "Your browser does not appear to support " + "the HTML5 Canvas element"; return; } //get a reference to the canvas element var canvas = document.getElementById("stageCanvas"); //copy the canvas bounds to the bounds instance. //Note, if we resize the canvas, we need to reset //these bounds. bounds = new Rectangle(); bounds.width = canvas.width; bounds.height = canvas.height; //pass the canvas element to the EaselJS Stage instance //The Stage class abstracts away the Canvas element and //is the root level display container for display elements. stage = new Stage(canvas); //Create an EaselJS Graphics element to create the //commands to draw a circle var g = new Graphics(); //stroke of 1 px g.setStrokeStyle(1); //Set the stroke color, using the EaselJS //Graphics.getRGB static method. //This creates a white color, with an alpha //of .7 g.beginStroke(Graphics.getRGB(255,255,255,.7)); //draw the circle g.drawCircle(0,0, CIRCLE_RADIUS); //note that the circle has not been drawn yet. //the Graphics instance just has the commands to //draw the circle. //It will be drawn when the stage needs to render it //which is usually when we call stage.tick() //create a new Shape instance. This is a DisplayObject //which can be added directly to the stage (and rendered). //Pass in the Graphics instance that we created, and that //we want the Shape to draw. circle = new Shape(g); //set the initial x position, and the reset position circle.x = circleXReset = -CIRCLE_RADIUS; //set the y position circle.y = canvas.height / 2; //add the circle to the stage. stage.addChild(circle); //tell the stage to render to the canvas stage.update(); Ticker.setFPS(24); //Subscribe to the Tick class. This will call the tick //method at a set interval (similar to ENTER_FRAME with //the Flash Player) Ticker.addListener(this); } //function called by the Tick instance at a set interval function tick() { //check and see if the Shape has gone of the right //of the stage. if(circle.x > bounds.width) { //if it has, reset it. circle.x = circleXReset; } //move the circle over 10 pixels circle.x += 8; //re-render the stage stage.update(); } </script> </head> <body onload="init()"> <div width="400" height="300" id="canvasWrapper"> <canvas width="400" height="300" id="stageCanvas"></canvas> </div> </body> </html>
You can download the code for the example from here.
As you can see, the code is pretty simple, and its structure is very similar to using the DisplayList API within the Flash Player.
There are a couple of things that are important to point out.
The EaselJS Stage instance wraps the Canvas element, and handles when and how items are rendered. The stage is only rendered when you call stage.update(), and for performance reasons, you should only call this if something has changed and you need to update the canvas.
The Ticker class handles time management. It will call a tick event on any object that has subscribed to be notified. This is analogous to the ENTER_FRAME event in ActionScript.
If you resize the canvas, then its contents will be erased. However, using EaselJS, all you need to do is call stage.update() after resizing in order to re-render the graphics.
Given the lack of Canvas support in Internet Explorer, it is important that you detect for Canvas support in the browser, and provide an appropriate fall back for the user. I have some simple code in the example above, or you can use the Modernizr JavaScript Library which provides an API for detecting support for HTML5 features.
Finally, this is an early release of the library, and thus APIs may (and probably will) change. In addition, some things which you might expect to work may not be supported yet. For example, there are currently no APIs to retrieve the height / width of a DisplayObject (you will have to figure it out yourself). However, in general, the library is very robust and has been used in some production projects.
Here are some resources which are useful if you want to start playing around with the Canvas element via EaselJS:
- EaselJS Homepage
- EaselJS API Docs
- Modernizr JavaScript Library (for detecting support for HTML5 features).
- Canvas Element Draft Specification
- Canvas Element (Wikipedia)
- Let’s call it a draw(ing surface) Good introduction to the low level Canvas API.
- HTML5 Browser Support Matrix
- HTML5 and CSS3 Readiness
- HTML5 Support in your Browser
UPDATED : (02/15/2010) : Updated to work with EaselJS 0.3.1 release.
Post any questions / comments in the comments section below.
Waiting patiently for someone to wire up Easel to a Javascript port of Signals.
Turns out nothing in this world is real until it broadcasts [Object object] to anyone who will listen.
Also: great work, Grant + Mike.
tf
19 Jan 11 at 5:54 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
As awesome as this stuff is, providing a fallback for 57% of your users is not really a fallback.
Bart
20 Jan 11 at 2:14 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Hi,
About ExplorerCanvas : it works but is too slow slow for eg. games.
ponce
20 Jan 11 at 3:09 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@ponce
Have you tested ExplorerCanvas with EaselJS? or just in general?
And yes, I have heard the same things around ExplorerCanvas and performance.
mike chambers
mesh@adobe.com
mikechambers
20 Jan 11 at 11:11 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Why is it necessary to call “stage.tick()” after the tick event is handled? Does that keep the ticks running? or does that redraws the “stage”?
Anyway, awesome library!!
Federico
20 Jan 11 at 1:59 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@Federico
You are confusing stage.tick() which redraws the stage, with the tick event broadcast by Tick (which manages time intervals).
Yes, it is very confusing.
I think that Grant is looking at renaming these APIs to make them less confusing.
mike
mikechambers
20 Jan 11 at 5:50 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
nice tutorial Mike! And I’m following the progress of easel.js with great interest!
I’ve been reliably informed that it’s best practice to cuddle your brackets in JS – apparently some browsers automatically add semi-colons at the end of every line so if you have something like :
if(condition)
{
stuff;
}
The browser may interpret that as :
if(condition);
{
stuff;
}
at which point your “stuff” gets executed irrespective of the condition! So always put your opening brackets on the end of the line.
It’s quite weird for me to get used to as well. I love dropping my brackets. I guess I’m just not a cuddler, but I’m working on my JS intimacy issues :)
thanks again
Seb
Seb Lee-Delisle
21 Jan 11 at 10:40 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@Federico, @mikechambers
Yeah, looks like all Tick’s listeners are called and are meant to be the “game tick”, i.e. physics, movement, etc, but not triggering any rendering, until you tick the stage itself. This gives you the flexibility to not redraw the stage every tick, but yes, it’s a little confusing.
What I was really confused about, at first, was this example. So there’s the init() and tick() methods, which aren’t designed in an object-oriented fashion, except that you pass “this” in to the Tick object at the end of init. This made sense once I realized that “this” corresponded to “window”, and that it was causing window.tick to be called by the Tick object. Maybe if you could wrap the whole thing in an actual object/prototype it would be more obvious, or simply passing window instead of this.
Max
21 Jan 11 at 10:53 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@seb
–
I’ve been reliably informed that it’s best practice to cuddle your brackets in JS.
–
Do you have a reference for this? In particular, I am curious which browsers this affects.
thanks…
mike chambers
mesh@adobe.com
mikechambers
21 Jan 11 at 5:47 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Hi Mike,
There’s a good lengthy discussion about this over at StackOverflow :
http://stackoverflow.com/questions/2846283/what-are-the-rules-for-javascripts-automatic-semicolon-insertion
And Douglas Crockford has a set of guidelines for coding JS that seem to be widely regarded as best practice :
http://javascript.crockford.com/code.html
cheers!
Seb
Seb Lee-Delisle
22 Jan 11 at 3:59 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@seb
Thanks for the link. I didnt see any discussion around issues with putting brackets on their own lines in the first link when the grammaer is correct (which is a great resource). The only potential issue I saw was around using return:
return"something";
turns into:
return;"something";
But, in the case of:
if(condition){
}
I didn’t see anything that would suggest the parser would interpret that incorrectly (although maybe I missed it). The key rule seems to be:
But, in the example you provided, the token / LineTerminator is allowed by the grammar. But again, I may have missed, or misread something. Now, maybe if I made a mistake, it could lead to some difficult to debug bugs, like:
var a = "";if(false)a
{
console.log("hi");
}
seems to be interpreted as:
var a = "";if(false);
a;
{
console.log("hi"); //this always runs
}
At least when I run it in Google Chrome.
I have a lot of respect for Crockford, but I dont always agree with him 100%, (such as how he prefers to handle inheritance). I personally prefer my brackets on their own lines, as I feel is is easier to read and track code blocks. But again, that is just my personal preference.
Anyways, really interesting stuff. Its feels like everything is coming full circle and im back to doing AS1.
mike chambers
mesh@adobe.com
mikechambers
22 Jan 11 at 12:48 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Hi Mike, I also don’t agree with Crockford all the time, particularly with tabs!
There does seem to be conflicting advice as to exactly where semi-colons are added and where they’re not. But that’s not really the point. As long as they’re added in some cases it makes sense to always cuddle your brackets, thus maintaining consistency throughout your code.
cheers
Seb
Seb Lee-Delisle
23 Jan 11 at 11:51 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@seb
I think on this one, we will just have to agree to disagree. But, yes, it looks like you can remove some potential hard to debug errors (such as my example above), by cuddling brackets.
mike chambers
mesh@adobe.com
mikechambers
23 Jan 11 at 12:41 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
[...] library created by Grant Skinner for working with the HTML5 canvas element. I was reading this post the other day and was wondering what it would take to add JQuery into the [...]
Playing around with the HTML5 Canvas Element using EaselJS and JQuery
24 Jan 11 at 8:54 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
[...] I posted the other day, I have been spending some time playing around with dynamic drawing with the HTML5 Canvas element [...]
EaselJS Example : Follow Drone at Mike Chambers
24 Jan 11 at 1:00 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
[...] I posted the other day, I have been spending some time playing around with dynamic drawing with the HTML5 Canvas element [...]
EaselJS / Canvas Example : Follow Drone (Adobe Flash Platform Blog)
24 Jan 11 at 9:40 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
For those of you that are using the new .31 version of EaselJS, the “Tick” obejct has been renamed “Ticker”, so in the code above:
Tick.addListener(this);
Change it to:
Ticker.addListener(this);
Doug Winnie
15 Feb 11 at 9:51 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
@Doug,
Good catch. Ill got through and update the code.
Stage.tick() is also now Stage.update().
mike chambers
mesh@adobe.com
mikechambers
15 Feb 11 at 10:45 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
I just updated the post to make sure it is consistent with the EaselJS 0.3.1 update.
mike chambers
mesh@adobe.com
mikechambers
15 Feb 11 at 3:17 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Hey Mike, just wanted to thank you for writing this post. It helped me to get into EaselJS fast.
I ended up creating an old-school 8-bit style animation accompanied by a great C64 audio track :) Check it out here:
http://blog.cubical.se/2011/05/chiptune/
Anders Kavcic
10 May 11 at 2:11 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
[...] Chambers wrote a great introduction to EaselJS: http://www.mikechambers.com/blog/2011/01/19/getting-started-with-the-canvas-element-and-easeljs/ 2011-05-10 Posted in: Experiments Leave a comment [...]
Chiptune Nostalgia - a HTML5 canvas experiment | Netdump
10 May 11 at 2:15 pm edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>
Hey, thanks so much for the tutorial. I have a question. How would you dynamically adjust the radius of the circle (or the height and width if it were a rectangle)? It doesn’t seem like the shape object gives direct accessors to those properties and not sure how you would redraw the shape — under the tick() function?
Thomas
4 Nov 11 at 11:27 am edit_comment_link(__('Edit', 'sandbox'), ' ', ''); ?>