Creating a rotating banner animation

Here’s what we’re building today:

We want to create an animation that rotates infinitely between a set of states. In most use cases, that’s probably between different background images, but for this article we’ll use background color.

Our first step is to build a set of keyframes, which represent each state in our set. In CSS, keyframes are placed at values between 0% and 100%, representing the beginning and end of the animation.

Since we’re using 3 stages, it might seem reasonable to split it to 0%, 50% and 100%:

But that ends up looking like this - notice the transition between Frame3 and Frame1 happens instantly.

This is because there’s no space between the 0% and 100% points in the animation. Essentially, the 0% and 100% points are the same, if the animation is going to repeat.

In order to solve this, we need to put more space between the last frame and the first frame. 100% divided by the number of frames = ~33%. So let’s change the placement to intervals of 33: 0%, 33% and 66%.

But now there’s a new problem. Watch how Frame3 fades to a different color altogether before switching to Frame1!

This is because, according to the CSS3 Animation Spec,

If a ‘0%’ or ‘from’ keyframe is not specified, then the user agent constructs a ‘0%’
keyframe using the computed values of the properties being animated. If a ‘100%’ or ‘to’
keyframe is not specified, then the user agent constructs a ‘100%’ keyframe using the
computed values of the properties being animated.

So even though we didn’t specify a 100% frame, there is a hidden 100% frame generated by the browser, populated with how the element would look without our animation properties. To solve that, we’ll just specify that the 100% frame should be the same as the 0% frame.

Now that we’ve figured out the keyframes, let’s talk about timing functions. The timing function is a mathematical function that describes how fast a one-dimensional value changes over the course of an animation. The default timing function for an animation is ease. Take a look at the MDN article on timing functions.

The ease timing function looks like this:

Notice how it is very curvy, and thus is constantly transitioning. This is ideal for something like moving an element or changing the width of an element, because the movement looks smooth:

However, for our banner, we want it to stay on one state for some time and then transition smoothly to the next one. Here’s how our banner would look with the default easing function:

The colors are constantly blending in to each other, and if there was text on one of the frames it would be unreadable. In order to fix this, we need to use some sort of timing function with a flat line, that sharply transitions to the next frame afterwards.

Lea Verou has an awesome tool at cubic-bezier.com that allows you to quickly graph and test cubic bezier timing functions. Using it I put together the function cubic-bezier(1, 0, 0.7, 0). With this timing function, we have all the tools we need to build the rotating banner.