Experimenting with Canvas and request­Animation­Frame

Or that giddiness you feel when something is way easier than you expected

See the Pen by @rodaine on CodePen.

Recently at work, I was checking out some designs for a client’s print piece, and was struck by this mesmerizing geometric pattern in the background. I mentioned to Taylor Gorman that it’d be awesome if – when these designs were translated to their site – the background was animated similar to the old bezier screensaver that used to come with Windows (it might still do?).

Up to this point, I hadn’t experimented with the new-ish canvas features of HTML5, so I figured I’d give it a go. Today, I’ll walk through the process I used to create this animation. Just a fair warning, I’ve come to love writing my JavaScript in CoffeeScript, so if you aren’t a fan, I encourage you to check out the generated JS on the pen. The entire source is also available as a gist, too!

The goal was to have a bunch of overlapping translucent triangles animate within the canvas. Each triangle’s vertices should animate independently to give the scene a very chaotic feel. Let’s see how we do…

Configuration & Utilities

The config object provides a bunch of static settings for tweaking the behavior of the animation, including the number of triangles to render, their speed, and the ranges for color and opacity for each of the shapes.1

#-------------------------------------------------------
# CONFIGURATION - Feel free to mess with these values
#-------------------------------------------------------

config =
  #RENDERING SETTINGS
  triangles:   30 # number of triangles to render
  speed:        2 # speed of animation ... approximately pixels per update
  
  #TRIANGLE COLORS
  redMin:       0 # minimum red value (0 - 255)
  redMax:     255 # maximum red value
  greenMin:     0 # minimum green value
  greenMax:    32 # maximum green value
  blueMin:      0 # minimum blue value
  blueMax:     32 # maximum blue value
  opacityMin: .05 # minimum opacity (0 - 1)
  opacityMax:  .2 # maximum opacity
 
# Clamps a numeric value between a minimum and maximum (inclusively)
clamp = (value, min, max) -> Math.min(Math.max(value, min), max)

# Gets a random number between min (inclusive) and max (exclusive)
rand = (min = 0, max = 1) -> Math.random() * (max - min) + min

# requestAnimationFrame compatibility/fallback wrapper
# a la Paul Irish: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = 
  window.requestAnimationFrame       || 
  window.webkitRequestAnimationFrame || 
  window.mozRequestAnimationFrame    || 
  window.oRequestAnimationFrame      || 
  window.msRequestAnimationFrame     || 
  (callback, element) -> window.setTimeout callback, 1000 / 60

clamp keeps numbers inside a range; this is particularly useful for keeping the triangle vertices within the canvas boundary. rand is a small random number utility to keep us DRY. And finally, requestAnimFrame is a compatibility wrapper for requestAnimationFrame.

Ok…so what the heck is requestAnimationFrame?

Back in the day, JS animations relied on setTimeout as the event/update/render loop for animations. While this worked, it was suboptimal especially if multiple animations were occurring simultaneously.

Thus, browser vendors implemented this new API which allows developers to update their animations right before a repaint. Our version of requestAnimFrame is brought to you by Paul Irish, which handles vendor prefixed versions of this method with a fallback to the old-school setTimeout method.

In the Beginning: The Vector

Our first class will be the Vector, which is somewhat of a misnomer. In this case, a Vector object is either used as a point or bearing on the canvas. Let’s dig into the definition:

#-------------------------------------------------------
# VECTOR - A point on the canvas or a bearing
#-------------------------------------------------------

class Vector
  x: 0
  y: 0
  
  constructor: (@x = 0, @y = 0) ->
  
  # Generates a random vector within the bounds
  @random: (bounds) =>
    x = rand bounds.minX(), bounds.maxX()
    y = rand bounds.minY(), bounds.maxY()
    new @ x, y
  
  # Detects whether or not this vertex is out of the bounds
  outOfBounds: (bounds) ->
    bounds.minX() >= @x ||
    bounds.maxX() <= @x ||
    bounds.minY() >= @y ||
    bounds.maxY() <= @y
  
  # Clamps the vector to a bounds
  clamp: (bounds) ->
    @x = clamp @x, bounds.minX(), bounds.maxX()
    @y = clamp @y, bounds.minY(), bounds.maxY()
    @
  
  # Adds another vector to this one
  addVector: (vector) ->
    @x += vector.x
    @y += vector.y
    @
    
  # performs a scalar product against the vector
  scalarProduct: (scalar) ->
    @x *= scalar
    @y *= scalar
    @
  
  # clamps and reflects a vector if it's out of bounds
  reflectAndClamp: (bounds, bearing) ->
    if not @outOfBounds bounds then return @
    if @x <= bounds.minX() or @x >= bounds.maxX() then bearing.x *= -1
    if @y <= bounds.minY() or @y >= bounds.maxY() then bearing.y *= -1
    @clamp(bounds)
    @

A Vector instance stores the coordinates (or bearing) in it’s x and y properties. The rest of the methods are helper functions related to boundaries, translations and reflections, just a bit of baby linear algebra.

Vector.random will generate a random instance within the boundary provided (we’ll cover the Bounds class next, don’t you worry). Since each of the triangles will be randomly generated, this function is more than just a fancy little toy.

Vector#outOfBounds tests to see if a vector instance is outside the limits of a provided boundary. This is important for collision detection after a translation has been applied to the vector.

Vector#clamp does the same thing as the scalar clamp function, but now forces the vector coordinates within the provided boundary.

Vector#addVector performs a vector addition by adding the provided vector to the current instance. Mathematically, an example would be {1, 1} + {1, 2} = {2, 3}. This is the method we will be using to translate the vertices of each of the trianges.

Vector#scalarProduct multiples a scalar against the current vector instance, e.g. 2 * {1, 2} = {2, 4}. This will be important for applying our speed factor set in the config object.

And finally, Vector#reflectAndClamp checks if a vector is out of bounds, and – if it is – reflects the bearing Vector instance depending on which side of the boundary it has crossed before clamping the current instance to the provided bounds. That is to say: collision detection.

All super exciting stuff, right?! I promise things will get more exciting, but first one more thrilling class…

Party gone out of Bounds!

This little class describes a rectangular boundary on the Cartesian plane, defined by two Vector instances at the top-left and bottom-right. It also includes a few helper methods to characterize the extrema:

#-------------------------------------------------------
# BOUND - A rectangular boundary for drawing
#-------------------------------------------------------

class Bounds
  topLeft:     undefined
  bottomRight: undefined
  
  constructor: (@topLeft = new Vector, @bottomRight = new Vector) ->
 
  minX: -> @topLeft.x
  maxX: -> @bottomRight.x
  
  minY: -> @topLeft.y
  maxY: -> @bottomRight.y

This one is super-straightforward, so we’ll skip ahead to the bread-and-butter of the animation…

The Triangle

Our meat-and-potatoes (I can do these food analogies all day, if you’d like), a Triangle instance is described by its three vertices & corresponding bearings, the bounds it’s contained within, and its color & opacity. For each update step, the triangle translates each vertex by its bearing, performing any collision detection necessary. A Triangle object is also responsible for rendering itself on the canvas context. Enough preamble!

#-------------------------------------------------------
# TRIANGLE - Polygon to be drawn onto the canvas
#-------------------------------------------------------

class Triangle
  red:        undefined
  green:      undefined
  blue:       undefined
  opacity:    undefined
  
  bounds:   undefined
  vertices: undefined
  bearings: undefined
  
  bearingBounds: new Bounds(new Vector(-1, -1), new Vector(1, 1))
  
  # creates a random triangle within the bounds
  constructor: (@bounds = new Bounds) ->
    @red     = Math.floor rand config.redMin, config.redMax
    @green   =  Math.floor rand config.greenMin, config.greenMax
    @blue    = Math.floor rand config.blueMin, config.blueMax
    @opacity = rand config.opacityMin, config.opacityMax
    
    @vertices = []
    @bearings = []
    
    for [0...3]
      @vertices.push Vector.random(@bounds)
      @bearings.push Vector.random(@bearingBounds).scalarProduct config.speed
  
  # updates the position of each of the triangle vertices by its bearing
  update: ->
    for i in [0...3]
      vertex = @vertices[i]
      bearing = @bearings[i]
      vertex.addVector bearing
      vertex.reflectAndClamp @bounds, bearing
  
  # renders the triangle in the canvas context
  # includes an optional partialStep for extrapolating lag position
  render: (context, partialStep = 0) ->
    points = []
    for i in [0...3]
      bearing = @bearings[i]
      vertex = @vertices[i]
      points.push
        x: parseInt(vertex.x + partialStep * bearing.x)
        y: parseInt(vertex.y + partialStep * bearing.y)
  
    context.fillStyle = "rgba(#{@red},#{@green},#{@blue},#{@opacity})"
    context.beginPath()
    context.moveTo points[0].x, points[0].y
    context.lineTo(points[i].x, points[i].y) for i in [2..0]
    context.closePath()
    context.fill()
    @

A new Triangle takes a Bounds to create a completely random instance. First, the fill color RGBA values are randomly selected from the ranges specified in config. Notice that the color numbers must be floored; any non-integer value for the RGB will result in opaque black triangles. Nooooot what we’re looking for. Next, random vertices are generated for the three points of the triangle. Likewise, random bearings are created from a boundary between {-1, -1} and {1, 1}, which are then scaled by the speed factor defined in config.

Triangle#update steps the instance one tick or translation, which we will define explicitly in the next section. Each Vector of the triangle is translated by it’s bearing, and collision detection is then performed on the point to reflect it’s bearing if it’s encountered an edge. That’s it!

Triangle#render, well, renders the triangle on the canvas. Two arguments are provided: the context, and a partialStep. The context handles actually drawing the shape onto canvas element, while partialStep will help us extrapolate if there is any lag in the time between the current request to render and the last update (more on this in a moment).

First, Triangle#render generates the points it needs to render. Basically, this is just the vertices of the triangle. However, sometimes the request to render is made midway between calls to Triangle#update, so the animation may appear jittery as it’s playing catch up. For example, suppose update is called every 30ms, but render is called every 45ms. The animation would lag the actual position of the triangle’s vertices by half an update.

To counteract this lag, Triangle#render is passed how many partial update steps – partialStep – the animation is behind. This will be a real number between 0 (completely in-sync) and 1 (exclusive). partialStep is then used to extrapolate the true position of the Vertex by translating it partially. Notice, that no collision detection is done, so it is possible the point may render outside of the bounds, but this will only exist for a single frame (hopefully) and the next render will be more closely caught up with the updates). Overkill? Perhaps considering the simplicity of this example, but it guarantees the smoothest possible animation.

Lastly, notice that the points’ coordinates are run through parseInt to get an integer value. When rendering to a canvas, using non-integers will result in really hideous anti-aliasing. You could probably also use Math.floor, Math.ceil or Math.round to the same effect.

Now that we have our points, we can actually draw them on the context, our canvas’ instance of CanvasRenderingContext2D. The fillStyle is set to the RGBA color for the triangle, a path is drawn between each point in the triangle and then filled with our specified color. Simple as that. We’re almost there; hang in!

The Developer’s Canvas

Okay, so probably not the best name for the class considering it’s not the actual <canvas/> element. A Canvas instance will wrap the element however, hold references to all the triangles to be displayed, and contain the update/render loop:

#-------------------------------------------------------
# CANVAS - The animation logic and wrapper for the canvas
#-------------------------------------------------------
 
class Canvas
  updateStep: 1000/60
  el: undefined
  context: undefined
  triangles: undefined
  previous: undefined
  lag: undefined
  
  constructor: (@el) -> 
    @context   = @el.getContext('2d')
    @triangles = []
    @previous  = new Date().getTime()
    @lag       = 0.0
  
  # add a Triangle to the stack to be animated
  addTriangle: (triangle) -> @triangles.push triangle
  
  # updates all the triangles on the stack
  update: ->
    triangle.update() for triangle in @triangles
    return
  
  # renders all the triangles on the stack
  render: (partial) ->
    @context.clearRect 0, 0, @el.width, @el.height
    triangle.render(@context, partial) for triangle in @triangles
    return
  
  # run at each animation frame
  # inspired by Bob Nystrom: http://gameprogrammingpatterns.com/ 
  loop: ->
    current   = new Date().getTime()
    @lag     += current - @previous
    @previous = current    
    
    while @lag >= @updateStep
      @update()
      @lag -= @updateStep
    
    @render @lag / @updateStep
    
  # start the animation loop using requestAnimationFrame wrapper
  start: ->
    window.requestAnimFrame => @start()
    @loop()

updateStep stores the time (in ms) between each update. It’s defaulted to 60fps (1000ms / 60fps = 16.67ms) but should be a value less than or equal to the time between frames (inverse of fps).

When a new Canvas is created, the 2d context is retrieved from the passed canvas el, and the other properties are set.

Canvas#update runs through all the triangles added via Canvas#addTriangle and updates their position one tick. Likewise, Canvas#render tells the triangles to render themselves on the context after clearing the previous frame. Remember, all the actual magic occurs over on the Triangle instances.

On to the update loop: Canvas#loop. The actual logic for this method was inspired by Bob Nystrom in his online book Game Programming Patterns; I strongly encourage you check it out, even if you aren’t interested in game dev. First, the current timestamp is obtained, and the time difference between current and our previous timestamp is added to the cumulative lag of our animation. Next, we attempt to perform an update for each updateStep in lag, attempting to catch us up with the real-time position of the triangles in the animation. Once the lag is reduced to below a single updateStep, Canvas#render is called and passed the ratio of lag to updateStep as the partialStep extrapolation value.

And, finally, the ON button for this whole contraption: Canvas#start. This method makes the self referencing call to our requestAnimFrame wrapper and then calls Canvas#loop. Why loop after the rAF? If the animation has fallen-back to the setTimeout method, calling it first will ensure we are as close to 60fps as possible.

Putting it all together

All that’s left is to bootstrap our animation onto a canvas element. We do rely on a bit of jQuery here (read: laziness), but depending on how you were to implement the canvas, it may not be necessary:

#-------------------------------------------------------
# THE MAGIC
#-------------------------------------------------------

# grab the canvas element
el = document.getElementById 'c'
$el = $(el) 

# gimme that full screen
el.width = $el.innerWidth()
el.height = $el.innerHeight()

# get the vectors to define the bounds
# giving a bit of wiggle room for overflow (makes for a prettier pattern)
topLeft = new Vector()
topLeft.x -= el.width * .25
topLeft.y -= el.height * .25

bottomRight = new Vector el.width, el.height
bottomRight.x += el.width * .25
bottomRight.y += el.height * .25 

# make the bounds for rendering the triangles
bounds = new Bounds topLeft, bottomRight

# create the canvas object with the canvas element
canvas = new Canvas el

# add in the triangles
canvas.addTriangle new Triangle bounds for [0...config.triangles]

# profit!
canvas.start()

First, we grab the canvas element in our document and set it’s actual width & height, to the CSS width & height. Why? Think of a canvas element like an image; you can use CSS to stretch an image to any size, but it will end up looking deformed. Same goes for a canvas, but you can modify it’s width & height prior to drawing on it to expand it appropriately. If you check out the pen, you’ll notice that I wanted the canvas to fill the screen, so this technique made sure the canvas dimensions were changed accordingly.

Next, based off these dimensions, we define the Bounds in which we will render the animation. You could simply set the boundary to be the same as the canvas element, but the animation is not as pretty (due to the many triangle points on screen at once). Instead, the bounds are set to account for some overflow by extending it 25% in every direction. I encourage you to experiment with these values for your own aesthetic.

Now, we create the Canvas object with our canvas element and add our triangles to the stack (the quantity of which we defined in config). Finally, to get things rolling, we make the call to Canvas#start. Profit!

What’s Next

There is a lot of room for improvement here. Looking at the memory profile in dev tools reveals there aren’t any memory leaks, but there is a pattern of garbage collection spikes resulting from discarded objects, most likely from the temporary points collection in Triangle#render. If we were to forgo creating those objects in the first place or cache those points on the Triangle instance (overwritting the coordinates on each render), those spikes would be minimized.

Another cool addition would be to also animate the color of each triangle. Switching the colors to a HSLA implementation would allow you to loop through the hue value similar to the actual bezier screensaver. Actually, I’m kicking myself for not thinking of it when I first implemented this. Blast you, 20-20 hindsight!

Lastly, it would be nice if it responded appropriately to DOM events, especially resize. Right now if you were to resize the preview screen on the pen, the animation will distort with it. Basic DOM event handling would avoid this.

Of course, I leave these as exercises for my dear readers (or future self, more likely). This was a fun foray into the land of JS/canvas animations, and I’m tempted to delve into the topic deeper. Perhaps, a game… more to come!


  1. Honestly, I wrote this portion last, but since I make reference to it throughout the code, it’s best to start here. ↩︎