Try it out here!

I mainly made this because I wanted to try out drawing in Illustrator, but I've also wanted to try recreating Github's cursor-based parallax (seen on their 404 pages) for a while now!

The scene is like a super-basic version of some art from Firewatch. To make it, I mainly just played around in Illustrator for half an hour or so. I probably drew about 10 birds of varying quality – I'm no artist so I roughly copied sketches I found on Google. I could have used my iPad for this but didn't want to pay any extra for a vector drawing app, so I just settled for the trackpad. I exported every layer as SVG – there's 9 in total, including the background gradient.

(By the way – there's a cool visual effect where it seems like the birds further away are being occluded by some kind of misty fog-of-war kinda thing. But they're not a different colour, blurred or anything like that – just smaller and less detailed.)

I took a look at how Github did it under the hood, and it seems like they use a jQuery library called Plax. That's cool, and it probably does some fancier stuff than what I'm looking for, but I want to write the code myself. Also, Github's animation clocks in at around 24fps on my Macbook Pro – we can do better.

GitHub's Parallax 404 Page

The advice I've heard for keeping within a tight frame budget is to only animate two properties: transform and opacity. Seriously. No background-color, and definitely no top or left. That's fine for this though – we'll just use transform: translate3D() and calculate some offsets.

Traditionally, to calculate what a camera sees, you'd have all sorts of matrices set up to calculate distance and camera angles and so on. But that seems like overkill – we're rendering a flat landscape here, so we can take a big shortcut in assuming the perspective is already "baked-in" to the image layers. The mountains are only about 6 birds high, for instance – not thousands, like they would be if drawn to scale. Also, we don't need to sweat the details when a lot of the image is just jagged edges of things rather than recognisable features like trees.

For this scene, I've just used each layer's index to calculate how much it should move away from its initial position. So layer 0 won't move at all, layer 9 moves the most. I take the layer index, divide by the total number of layers, then multiply by some constant value which represents the furthest distance anything can move on screen.

The code to calculate and apply the parallax is super short: I've included it in full below.

I also added a couple of event listeners to track the mouse position (and the device orientation for mobile, although I haven't really spent enough time on it for mobile yet).

The end result is super pleasing, and super performant – it easily stays at 60fps even when dragging the mouse around like crazy!

One thing I also wanted to try was blurring the mountains slightly. CSS has  filter: blur(_px), which was my first port of call. However when I applied it to the furthest mountain – even though that property isn't being animated at all – I saw the framerate drop to 20-30fps. Not ideal.

Fortunately, SVG has its own way of applying filters, and after a quick tweak in Illustrator, the background was blurred – and the framerate was back at 60. Great!

Side note: as well as the framerate, the load time of this little experiment is super quick. Chrome records this as getting to interactive within 100ms, which is just unreasonably speedy. I'm putting this down to using vector images instead of rasters – and just for total speed-gluttony I added a gzip directive to the Caddy instance serving this webpage, so even the text assets are served super-fast.

Things that probably should be different:

  • Exporting a background gradient as SVG instead of just using CSS linear-gradient
  • Translating the background layer by (0, 0) instead of just skipping it altogether

But I'm not really bothered about either of them – the target is to hit 60fps and it does just that! (The browser probably optimises away zero-translations anyway.)

Have a play around with it and let me know what you think!