Zoompf's Web Performance Blog

Note: Archived Content

This is the archived version of the Zoompf blog. Since our acquisition by Rigor, all our new research and posts on web performance are being published on The Rigor Blog

How Do Google’s Animated Doodles Work?

 Billy Hoffman on April 23, 2012. Category: Optimization

Sunday was Earth Day. As with many holidays or anniversaries, Google celebrated Earth Day with an animated doodle. This is certainly not the first animated doodle that Google has done. Since I have been speaking so much about images recently, I thought it would be interesting to see how Google creates small and fast animations. The solution surprised me, and changed my mind about animated images on the web.

What Google is doing

The Google Earth Day animation is a non-repeating linear sequence of images. Unlike previous animated doodles, it is not interactive. The animation is 486 pixels wide by 182 pixels tall. Traditionally, this could be accomplished using an animated GIF image. In fact, that is what I thought they were doing at first. However Google took what I think is a different and better approach.

Instead of an animated GIF Google stored all the animation frames vertically inside a single JPEG image that is 468 pixels wide by 2912 pixels tall. There are 16 frames of animation stored this way, somewhat resembling a strip of film, as shown in the screen shot below:

filmstrip image opened in the GIMP

This is kind of like a CSS sprite, but each image is a frame of animation. To clarify things, I will call this animation technique filmstrip animations. Another animated doodle which uses filmstrip animation is this Gumby logo from October 2011.

So how does Google make the filmstrip animation work? First they enclose the <img> tag JPEG with all the animation frames inside a <div> tag that has a fixed height of 182 pixels and which hides overflow. This creates a fixed window so to speak, which masks all but current animation frame. The image is animated using JavaScript, which changes the top property for the absolutely positioned image to slide it up a fixed interval with the setTimeout() function. A diagram of this concept is shown below:

Diagram of how filmstrip animation works

The code for this looks something like this:

<div style="height:182px;position:relative;width:468px;overflow:hidden">
	<img border="0" src="source.jpg" id="filmstrip" style="position: absolute; height: 2912px; top: -0px; display: block; ">


function naiveAnimation(id) {

	var img = document.getElementById(id);
	var offset = 0;
	var animate = function() {
		//slide the image correct frame of animation given by  offset
		img.style.top = -offset + "px";
        //calculate offset to next frame
		offset = Math.floor(offset + 182);
		//if we are not yet on the last frame...
		if(offset < 2912) {
			//call me again in half a second
			window.setTimeout(animate, 500);
		} else {
			//at last frame, so all done!
	//start the animation



However, this animation is jerky compared to Google’s Earth Day. In Google’s animation, the frames seem to fade into each other. How is Google doing this?

Generating Intermediate Frames

Google uses a neat trick. They use opacity to fade between two different frames of animation and create, in effect, additional intermediate animation frames. This makes the animation look smoother.

To accomplish this, Google uses two <img> tags inside of the container <div> and uses absolute positioning and z-index to place them directly on top of each other. You can see this clearly using Firefox’s 3D DOM viewer, as shown in the screen shot below:

Firefox's 3D DOM Viewer

Each image is pointing at the filmstrip image. Let’s call the “lower” image tag <img> #1 and the “upper” image tag <img> #2. To accomplish the effect, <img> #1’s top property is set to show animation frame 1 and <img> #2 is set to show animation frame 2. The opacity property of <img> #2 is set to 0, making it full transparent so animation frame 2 is not seen. This setup is shown in the diagram below:

Fading animation effect explained

Google then uses a fast JavaScript timer to increase the opacity of <img> #2 every 50 milliseconds. Increasing the image’s opacity makes it less transparent, so more and more of <img> #2 is visible and less of <img> #1 is visible. This makes animation frame 2 fade in on top of animation frame 1, as shown in the screen shot below:

Fading animation effect explained (part 2)

Google slowly fades the image in over 10 steps of 50 milliseconds. After 500 milliseconds:

  • <img> #2, displaying animation frame 2, is fully opaque and covers <img> #1.
  • The top property of <img> #1 (which is now hidden by the fully opaque <img> #2 ) is increased so that <img> #1 now points at animation frame 3.
  • JavaScript is now used to decrease the opacity of <img> #2 every 50 milliseconds This means <img> #2 (displaying animation frame 2) slowly becomes transparent, allowing <img> #1 (now displaying animation frame 3) to fade into view.
  • The process repeats

In short, using two images, changing opacity up and down, and moving the position of the two copies of the same filmstrip images, Google can create an animation with fading transitions between frames!

But what about animated GIFs?

My initial thought was that Google should be using an animated GIF. Why didn’t they? To find out I needed to create a GIF animation of the same content as the Earth Day filmstrip animation and compare the two. I took the filmstrip JPEG and sliced it into 16 separate images using Imagemagick’s convert -crop command. To create the animated GIF, I used GIMP, inserting each animation frame as a separate layer of a single image. I then exported it as an animated GIF with 500 milliseconds between frames. The resulting animation is shown below:

Google Earth Day animation as an animated GIF

The animated GIF approach was bad for several reasons.

  • GIF’s are not ideal at storing photographic images and all the frames of the animation must share the same palette of 256 colors.
  • The resulting GIF image is huge at 980 KB! GIF animations can be optimized to store the differences between frames. However, since my source image was a JPEG, the color values for pixels vary between each frame. In other words, the color value for the dirt color in the top left corner of each animation frame is different. This means there is little commonality between frames, so more graphics data must be stored for each frame, resulting in a large image. Since the original sources of each animation frame were photographs taken of a 3 dimensional biological scene (with different lighting effects and shadows, etc), I think it’s unlikely that having access to the original source images would have allowed me to create a more optimized animation.
  • The animation is jerky. It is only 16 frames and after one complete frame is shown it is replaced with next complete frame 500 milliseconds later. The animation runs at 2 frames per second.

Contrast this with Google’s JPEG filmstrip approach:

  • The image is a JPEG, allowing millions of colors and photographic realism.
  • The JPEG animation is only 270 KB, plus a few hundred bytes of HTML and JavaScript.
  • Google’s animation is much smoother. Even though the JPEG contains only 16 frames, the fading transition between frames occurs in 10 “steps.”. This essentially creates a 160 frame animation running 20 frames per second. Our animated GIF would grow significantly larger to create this same effect.

A New Animation Format?

Animated transitions have been popular with image galleries and carousels. However, the intended effect was not to create a single smooth piece of animation. By coupling tradition transition animation with a filmstrip of animation frames, Google has created a new and highly adaptive animation format for the web which is supported by every modern browser. A single image is used to store the animation frames and JavaScript advances the frames and drives addition effects. Consider the benefits of filmstrip animations:

  • Near universal compatibility. No new image format needs to be supported.
  • Use the best image format for the job. Need photorealism? Use a JPEG. Need an animation with alpha transparency? Use PNG! GIF animations cannot adapt like this.
  • Reduced size. The number of animation frames can be reduced by using JavaScript to generate intermediate frames. In our example, Google used opacity for fading, but this is just one method. Wipes of any kind are possible, in addition to moving specific parts of the image.
  • JavaScript can adapt to the platform. Look at navigator.network.connection and see you are on a slow network? You can respond and fetch a lower quality filmstrip image, or one with fewer animation frames.
  • Better control over frame order. With animated GIFs, you move from the first to the last frame, and then optionally start over again at frame 1. With JavaScript you can go from the start to the end, and then backwards for the end to the start. This allows you to store symmetrical animations in half the space. Or you could revisit only certain frames. This is more flexible, allows for better transitions, and avoid sharp jumps when the animation repeats.

But it requires JavaScript!

No, not really. You can still have an animated GIF inside of a <noscript> tag like this:

<div id="filmstrip-con">
	document.write("<img src='filmstrip.jpg' id='filmstrip-img'>);
	<img src="animated.gif">

(Yes, document.write is a bad idea here, but I’m using it for brevity.)

This is a big win all around. If even only 10% of your visitors have JavaScript enabled, that is 10% of visitors who don’t need to download a 1 megabyte GIF image. Anyone who doesn’t have JavaScript enabled can still see the animation, it will just load slower and potentially provide a lesser experience than the filmstrip animation.

But Why Not a CSS Animation?

If GIF animations are one extreme, CSS animations are at the other end. I don’t like CSS animations for a few reasons. The first is that browser compatibility, especially backwards compatibility is lacking. But the biggest is that the client is the one creating the animation. This is because CSS animations are extremely granular and microscopic. “Move this element in this way, at this time. Move this other element a different way at a different time.” With animated GIFs or the filmstrip approach, the content of the frames and how it changes are generated by the author, and a series of static images are presented to the client to draw.

With CSS animations, the animations are created on the client, so the computing power of the device greatly affects the experience. If the browser does not support hardware accelerated CSS transforms CSS animations are jerky and provide a poor experience. Additionally, all that microscopic “Move X to Y” makes creating and maintaining and updating animations difficult. While the filmstrip approach is more involved than simply exporting an animated GIF, it is clearly easier than composing an animation from DOM elements and coordinating their movement.

Filmstripper: A naive implementation

If you’d like to use filmstrip images, I’ve create a small proof of concept, filmstripper, that abstracts away specifics for you. The focus is on being as unobtrusive as possible. That means no external library dependencies and minimal markup or required tag decorations. Even though it’s fairly primitive (I haven’t added Google’s fading effect yet) this approach works well. If JavaScript is not enabled or an error occurs, a static image is shown. This allows animation to be added responsively.

To use filmstripper, create a filmstrip image with all the animation frames arranged vertically. Next, create a static image just containing the first frame of the animation. The code HTML is minimal:

<div id="filmstrip-con">
	<img src="static.jpg" id="filmstrip-img">

To animate, just call filmstripper(). The code is below:

 * filmstripper - Simple filmstrip based animations
 * contID - ID of DIV container element
 * imageID - ID of IMG element to animate
 * stripImageUrl - URL to the filmstrip image (frames arranged vertically)
 * delay - delay in milliseconds between frames
function filmstripper(contID, imageID, stripImageUrl, delay) {

	var con = document.getElementById(contID);
	var img = document.getElementById(imageID);
	var frameHeight = img.height;
	var throwAway = new Image();
	var setup = function() {
		//setup the div to hide the strip
		con.style.cssText = "height:" + frameHeight + "px;position:relative;width:" + img.width + "px;overflow:hidden";
		//setup the image to scroll frames, and switch static to strip
		img.style.cssText = "position: absolute; height: " + throwAway.height + "px; top: -0px; display: block;";
		img.src = throwAway.src;
		var d = 0;
		animate = function() {
			img.style.top = -d + "px";
			d = Math.floor(d + frameHeight);
			if(d < throwAway.height) {
				window.setTimeout(animate, delay);
	//request the film strip image
	throwAway.src = stripImageUrl;
	//launch everything if it loads
	throwAway.onload = setup;


Animated GIFs, while simple and widely supported, have several short comings. Google uses a single image containing arranged as a filmstrip and JavaScript to advance to animation frames. This approach allows for better, smoother animations which require less content to be delivered to the browser. If you have large animated GIFs, consider replacing them a filmstrip animation instead.

Want to see what performance problems your website has? Zoompf can analyzed your website for nearly 400 issues which affect web performance and load time. You can get a free performance scan of you website now and take a look at our Zoompf WPO product at Zoompf.com today!


Have some thoughts, a comment, or some feedback? Talk to us on Twitter @zoompf or use our contact us form.