4 min read
THE OPENING SLIDE OF A RECENT TALK I GAVE
Next in trending

CSS filters, GIFs, and performance

Or, how to persist in not learning Photoshop

CSS filters, GIFs, and performance

Or, how to persist in not learning Photoshop


A few months ago, I blatantly ripped off Charlie Gleason’s idea. His presentation at MelbJS featured greyscale, full-screen, animated GIFs (of his own face, among other things) behind the text on many slides. I discovered that he’d painstakingly edited and optimised each GIF in Photoshop so it looked good behind the text on each slide.

I wanted to do the same thing, but without all that work.

The opening slide of my presentation

My presentation about strict mode in Javascript at Web Directions Code a few months ago was the result. You might want to open that in a tab now because I use 24MB of gifs and it can take quite some time to load. Oh, and because it was only built to run on my machine, it may not work at all on yours. Sorry about that. (If you’re interested in the topic, here’s a video of the talk).

This post is about some of the things I learned along the way.


GIFs are Awesome

If you’re trying to convey an emotion in a presentation, it’s hard to beat a well-chosen GIF. Alternatively, if you’re procrastinating instead of writing your presentation, searching for the perfect GIF will distract you for hours. It’s a win-win.

Sneakers (1999)
<3: IWDRM.tumblr.com

Most GIFs out there are poor-quality, badly optimised and jarring when looped. But not all. Blogs like If We Don’t, Remember Me and Tech Noir prove just what’s possible with the format, capturing the feeling of a particular scene from a film in a way no static image can (note: All Movie Gifs has a great list of film GIFs, but of usually far lower quality). So the lesson is, good GIFs are out there: find them!


CSS Filters + Fullscreen GIFs are Slow

I’m not sure if that headline will surprise anyone. But it turns out, trying to render 4MB of image data in a file format defined in 1989, then stretch it over the millions of pixels in your display, then apply a Working Draft CSS Filter spec that’s only supported in Webkit browsers, and even then only recently, you’re in for a bad time.

How bad? This bad. Maximise this and feel the slowness. Jump into Timeline mode and watch that 60fps (and even the 30fps) target sail past:

Timeline profile for the ‘Slow’ version.

Blur is the culprit, and it hates pixels

After a bunch of experimentation, and with a variety of GIFs, the real problem was using the blur filter, which is a shame since that’s quite a useful one if you’re trying to make a background out of a GIF. So we have a problem.

But this, like all CSS problems, can be solved with MOAR CSS!


A magical conflagration of transforms and filters

I honestly don’t know if this is a “good idea”, but it solves this particular problem. My reasoning goes like this:

  • The GIF isn’t very big, dimensions-wise
  • Scaling up the image takes work
  • Applying the filter takes work proportional to the number of pixels
  • Why not apply the filter first?

It turns out that that’s indeed possible, and using CSS transforms we can scale up the image using the GPU, which gives us a reasonable performance boost.

Firstly, we make the background element smaller, say 20% of the viewport, rather than filling the full width:

position: absolute;
top: 0;
left: 0;
width: 20%;
height: 20%;

Then, we apply the background like we did before:

background-image: url(...);
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;

Then we apply the filter and scale the image up to fit:

-webkit-filter: blur(2px) grayscale(0.8) brightness(0.8);
-webkit-transform-origin: top left;
-webkit-transform: scale(5);

And then we’re done:

Timeline profile for the “fast” version

At first glance, it looks like we only sped things up by 40% (160ms/frame down to 100ms), but this is actually a 10fps GIF, so Chrome is just waiting until the next GIF frame is ready. All our painting, blurring and scaling is actually happening in the first 30ms.

Hooray for GPUs!