I recently posted an HTML5 canvas clock made of particles, but did not optimize the code. Here I present a much more optimized and visually engaging version of the clock. I’ve added many more particles, and made a few changes to the coloring scheme and behavior of the particles. Click here or on the screenshot below to see it in action, and then read below to see how I implemented pre-rendering to greatly improve performance. (A mobile version with a slower framerate and far fewer particles can be viewed here.)

Many of the details behind the code can be found in the emebedded comments, and also in my previous post. The big change in the code here is that the numeral particles are now being drawn by copying from an off-screen bitmap (or sprite sheet), instead of using the fillText() method. This one change has made a significant impact on performance.

There are some stylistic differences as well. I gave the particles varying colors depending on their value (so all zeros are the same color, all ones are the same color, etc.) with a gradient function. Check the setNumeralColors() function inside the code to see how varying blues and greens are created with cosine functions.

To make things more interesting, I have changed the way the particles behave. When a particle flies into place to become part of the displayed time, it repels the other inactive particles by an inverse-square force law. But the force varies according to how far apart in value the numerals are from each other. So for example, a 1 repels a 6 more strongly than it repels a 2. The numerical difference is considered in terms of modulo 10 values, so for example the difference between 1 and 9 is only 2. Because of the differing colors of the numerals, we see some color patterns emerge when time particles fly into place.

### The sprite sheet

The sprite sheet is generated at run time, rather than being a prepared image that is loaded by the code. The sprite sheet takes the form of an off-screen canvas element, following a method outlined at HTML5Rocks (check the link for more performance tips!).

First, we create a new off-screen canvas, with the right size to contain our numerals. Each numeral will occupy a space with width blockWidth and height blockHeight:

spriteSheetCanvas = document.createElement('canvas');
spriteSheetCanvas.width = blockWidth*10;
spriteSheetCanvas.height = blockHeight;
spriteSheetContext = spriteSheetCanvas.getContext("2d");

We set some font attributes using a font defined earlier while making sure the text will be vertically and horizontally centered at its drawing position:

spriteSheetContext.font = numberFont;
spriteSheetContext.textAlign = "center";
spriteSheetContext.textBaseline = "middle";

Then we draw the numerals to this sprite sheet canvas, using colors that have already been stored in an array:

for (var i=0; i<10; i++) {
spriteSheetContext.fillStyle = "rgba("
+ offRed[i] + ","
+ offGreen[i] + ","
+ offBlue[i] + ","
+ minAlpha + ")";
spriteSheetContext.fillText(i,i*blockWidth+sOffsetX,sOffsetY);
}

Note that we are applying some offsets in the x and y directions (that will be reversed when we draw the particles to the displayed canvas), in order to fit the centered text within the sprite sheet.

### Drawing the particles

Later in the code when we draw the particles, we copy from the sprite sheet we created above. (The particles which are becoming part of the displayed time or which are exiting the displayed time must be drawn differently, so we explicitly use the fillText() method to draw these, although we could also use a more involved sprite sheet for these particles as well.) Here is how we copy from the sprite sheet:

context.drawImage(spriteSheetCanvas,
p.value*blockWidth,0,
blockWidth,blockHeight,
Math.floor(p.x-sOffsetX),Math.floor(p.y-sOffsetY),
blockWidth,blockHeight);

The nine inputs in the drawImage() method set the spriteSheetcanvas as the source, the position and size of the rectangle within the source from which to copy, and finally the position and size of the rectangle into which to draw the source pixels. Note that we are applying the Math.floor() function to the coordinates of the destination rectangle, in order to avoid CPU-slowing sub-pixel rendering.

### One more optimization

In the last version of this clock I separated the coordinate updating code from the screen updating code for the sake of simplicity, but this was admittedly rather inefficient. Separating the code like this requires two runs through the arrays which store the particles. I have merged all of the updating and drawing code into one loop through the particle arrays, which not too surprisingly has also had a notable effect on efficiency.