HTML5 Canvas Layering

by


I have recently begun to study a couple different ideas to ease the pain of more complex animations using the HTML5 canvas element. Primarily, I have been focusing on layering – stacking individual transparent canvas elements – to achieve a robust effect and help manage individual objects without having to redraw the entire pane with every refresh. In the orbit example above, the center sphere and background are drawn statically on the bottommost layer, while the animation of the satellites is calculated and drawn on a second layer. By clicking on the demo, you can toggle the visibility of the animation layer. If you cannot see the animation at all, you may want to consider picking up a real web browser: Firefox or Chrome are always good options.

So, why bother with layering? Is it any more efficient than rendering everything within a single canvas element? Honestly, I don’t think it is. If memory optimization and performance is your absolute #1 concern, layering may not be the best answer; however, if you’re moving from a layered animation technology – such as Flash or Silverlight – and you’re wanting to try and keep the modular animation techniques with which you are familiar, layering really helps make the transition easier.

How does it work? Simply enough, the concept is to stack a transparent canvas element for each layer required by absolutely positioning them within a container div. In my code example, I have written my engine to allow the user to define either a div ID or canvas element ID as the “region”. If a canvas element is provided, then it is wrapped in a div, and that new parent element is then used as the region. By setting the positioning of the parent div to “relative”, we can absolutely position all canvas elements within it and let them stack. The comparison to set up the environment is something like this:

self.setRegion = function(id) {
    self.id = id;
    var el = document.getElementById(id);
    
    // If a canvas element is provided, wrap it in a div
    if (el.tagName.toLowerCase() == 'canvas') {
        $(el).wrap('<div class="canvas-holder-wrap" />');
        el.parentNode.style.position = 'relative';
        self.region = el.parentNode;
        self.background = self.createLayer(el);
    } else {
        self.region = el;
        self.background = self.createLayer();
    }
            
    self.region.style.position = 'relative';
};

As you can see, by default, there is a single layer applied to the self.background member variable of the engine. The self.createLayer() does the magic of applying a new canvas element to the markup and activating said element so it will be sure to render in IE. Since the newly created layer is returned, you can quickly set up the logic to draw your elements on that layer, and the layer object itself has some wrappers to make drawing a bit simpler. For instance, you could do something like this:

var display = new GW.Display();
display.setRegion('region-id');
display.background.fill('rgb(215,215,215)');

var l = display.createLayer();
l.drawCircle({x:50, y:50}, 30);

This will fill the background layer with a light gray, create a new layer and draw a 30 pixel radius circle centered at coordinates 50×50. Another benefit to using a Layer object like this is that each layer can then handle its own redraw. In my case, I’ve built an animate function that is called from a custom event in the parent Display object. When a redraw is called, the Display loops over all child layers and calls the Layer.animate() method on each one. By defining a handler as this method, you can easily make a layered animation. Something like this would cause a simplistic animation sequence:

var display = new GW.Display();
display.setRegion('region-id');
display.background.fill('#eeeeee');

var l = display.createLayer(),
    min = 50,
    max = 150,
    speed = 1,
    x = min,
    y = 30;

l.animate = function(layer) {
    if (x < min) {
        x = min;
        speed *= -1;
    } else if (x > max) {
        x = max;
        speed *= -1;
    }

    x += speed;
    layer.clear();
    layer.drawCircle({x:x, y:y}, 10);
};

display.play();

Obviously, with the simplicity of this example, we would be just as well suited to redraw the background each time, but the flexibility of this mechanism should be clear. For those who are very interested in the actual JavaScript objects I’m using to create the Display and Layer elements, feel free to view the source of this page. All inclusions and execution for the code in the example appears in the markup directly following the canvas element itself.

As I refine the technique and have some more time to pour into testing optimization, I’ll try to generate an actual API and offer the core as a base on which animators can build more easily. If this is a help to you or you use this in some method, I’d love to see your work! Please leave me a comment with a link to your animation.

Enjoy!


No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment