2013-12-19



If you sent or received Square Cash recently, you might have noticed slight precipitation backdropping our email headers. It all started last week when we decided to do something special for the holidays. I reckon my teammates thought I was joking when I shouted out with glee, “falling snowflakes!” But then I followed up with a working prototype. What says “Happy Holidays!” better than parallax-scrolling, alpha-composited, bokeh snowflakes, amirite?

The effect is simple. Writing the code behind it was anything but. Hacking together a proof-of-concept took moments. Profiling and optimizing the rendering and file size to achieve the final result was harder than it looks. The challenges were many, but we Square engineers pride ourselves on our ability to push the envelope at every layer of the stack. The question here was, how do we maximize compatibility and performance without compromising aesthetic?

Most email clients don’t support HTML5. We’re stuck with 90s era technologies, or in this case, animated GIFs. Obviously the animations should look crisp and beautiful, especially on today’s high resolution displays. We render them at 2X resolution—more than 300k pixels per frame. The animations include custom text. This requires us to render them on the fly and poses some fun challenges. Once rendered, the animation needs to download and play quickly, even over slow mobile networks.

To start, I pored over the 25-year-old GIF spec, reading it backwards and forwards, looking for opportunities to cut down the file size without compromising the design. I used pngquant’s best-of-breed Median Cut quantization algorithm to precompute an optimal 16-color palette. Restricting the animation to 16 colors enabled us to encode the images using 4 bits-per-pixel1. It also resulted in slight posterization, promoting color repetition and further improving compression, particularly in the gradient at the bottom of the image. I even tried plotting only the pixels that changed between each frame, but to my surprise, the diffs resulted in more complexity, less repetition, and therefore worse compression than the full frames.

The biggest win came from an optical illusion aimed at reducing the total number of frames. The animation looks like it necessitates 240 frames, but it really only requires 60 (a 75% savings). The animation is composed of four layers of snowflakes, each meant to appear a different distance from the observer. As the layers get further away, the snowflakes get smaller, lighter, slower, and denser, resulting in the illusion of depth. Here are the separate layers ordered closest to furthest:





The trick is only the closest layer travels the full height of the frame. The lower layers travel only a fraction of the height, but they tile and repeat, giving the illusion of continuity. For example, during the course of the animation, the second layer travels only half way down the height of the image, but it repeats twice and looks like it scrolls continuously. The third layer travels one third of the way and repeats three times, and the fourth layer travels one fourth of the way and repeats four times. As you can see, this repetition becomes less evident when you layer the snowflakes on top of each other and animate them at different speeds.

The final animation clocks in at less than 650KB. To see it in action, send some Cash to a loved one. And check out the final code below. Consider it my gift to you! Happy holidays.

It may sound counterintuitive, but using 4 bit-per-pixel can result in a larger file size than using 8 bits-per-pixel. Which encoding is better depends on which bit width produces more repetition in the bytes, and that depends on the nature of the image. The easiest strategy is to try both and see which results in a smaller file. ↩

Show more