Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

HTML5 Canvas Self-drawn Persistent Background

3.80/5 (4 votes)
5 Jul 2011CPOL4 min read 38.7K   533  
How to make an HTML5 canvas draw its own background layer

Introduction

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 webserver).

Here's a (non-animated) screenshot of the attached sample:

Image 1

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.

Background

A basic understanding of HTML/CSS/JavaScript is required, and an exposure to the HTML5 canvas is useful. Additionally, you'll need a browser that supports HTML5 canvas, such as Firefox, IE9, or Chrome.

Using the Code

The Basic Technique

As mentioned above, the gut of the technique lies in canvas.toDataURL():

C#
// Retrieve the canvas' drawing context
var canvas = document.getElementById("some-canvas-id");
var context = canvas.getContext("2d");

// Draw your background on the canvas
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
// More background stuff, of course....

// Then, once you've fully composed your background, grab it as a base64 PNG
var base64 = canvas.toDataURL();
// ..  and stuff that PNG into the element's background
canvas.style.backgroundImage = "url("+base64+")";

// You can now repeatedly clear & redraw the canvas without losing the background.

The Sample Code

The attached sample consists of 4 files:

  • index.html - declares the canvas element, sets things up in onload(), then runs some simple JavaScript on a timer to animate the ball.
  • 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 Point and Rect classes that aid in drawing.

Most notable are Chart.createBackground() and 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 moveto() and 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 crispLine() and 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.

History

  • 3rd July, 2011: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)