I had a need for an HTML5 canvas that displays a graph. The graph needed to automatically calculate its size on page-load (based on the canvas element's width and height), have a title, and show a scaled grid with x and y legends. Finally, and most importantly, the graph's data needed to update several times per second.
Because of the dynamic sizing, I couldn't use a stock background-image; I wanted the canvas to compose its own background on page-load. And for efficiency, I didn't want to redraw the background (legends and grid) each time I updated the graph line. Clearly, this calls for two layers, one for the background and a higher layer for the data. Unfortunately, canvas doesn't support layers, so no luck with the obvious/easy answer.
I poked around and saw suggestions for using multiple "stacked" canvas elements using absolute element positioning and z-index, but even that seemed overly complex for what I was doing. Then I found the
canvas.toDataURL() method and had an idea: Why not have the canvas dynamically draw its background on page-load, grab that image via
toDataURL(), and stuff that image into the canvas element's background-image?
Turns out it works great; this article presents my solution.
The attached sample is a simple implementation of the technique. (You can unzip the sample and point your browser at the index.html and it will run without a
Here's a (non-animated) screenshot of the attached sample:
The chart's text and gridlines are part of the background image; the red ball is periodically redrawn to bounce around inside the grid's boundary.
Using the Code
The Basic Technique
As mentioned above, the gut of the technique lies in
var canvas = document.getElementById("some-canvas-id");
var context = canvas.getContext("2d");
var base64 = canvas.toDataURL();
canvas.style.backgroundImage = "url("+base64+")";
The Sample Code
The attached sample consists of 4 files:
- index.html - declares the
canvas element, sets things up in
- canvas.js - implements my "
Chart" class, which wraps up all canvas' background declaration & drawing.
- canvashelpers.js - implements helper functions for drawing "crisp" lines on an HTML5 canvas (for details, see "Points of Interest" below).
- primitives.js - implements
Rect classes that aid in drawing.
Most notable are
Chart.clear() (both in chart.js):
createBackground() draws my custom background. This will of course vary by application, but it outlines some basic ideas that should be common to most uses.
clear() is a simple tidbit that's very useful to my animation code. It turns that a canvas' context gets cleared (erased) whenever the canvas' height is set, even when it's set to the current value.
Points of Interest
While the HTML5 canvas draws lines in the "usual way", via
lineto(), its handling of antialiasing these lines is something I'd not encountered before. Typically, if you did e.g.
moveto(10,10); lineto(100,10); you'd get a crisp 1 pixel tall horizontal line. But with canvas, this would result in a "blurry" (antialiased across 2 pixels) horizontal line... not at all what I expected. It turns out that canvas uses a floating point (not
int) address space, and in order to get the expected crisp 1px line, you must use half-point offsets, e.g.
moveto(10.5, 10.5); lineto(100.5, 10.5);.
It took me a while to understand what was happening; this page explains it well.
My sample code includes the helper functions
crispRect() (in canvashelpers.js) which automate the handling of this half-point offset to yield the crisp lines that I'd expect.
In the course of this project, I did a lot of experimentation with HTML5's many features (not just
canvas) and found that Chrome has by far the best support. Chrome supports everything well (and efficiently), Firefox supports most stuff (though not all, and is a memory pig), and IE9 is... still IE: it supports some stuff, doesn't support other stuff, does something differently, and in the end, it's not usable for a real HTML5-based app.
<rant>Why, oh why, can't the IE team ever produce a compliant browser? It's a crying shame that the world's most popular browser seems permanently destined to ride the short bus.</rant>
Additionally, I found that IE9 will not properly render things unless you begin the HTML file with
<!DOCTYPE html> whereas Firefox and Chrome don't require the
DOCTYPE attribute (took me a while to figure that out, BTW). Dunno who's right in this case, but once again IE's differences are a time-waster.
- 3rd July, 2011: Initial version