“Overlapping action” is one of the 12 celebrated Disney principles of animation, closely related to “follow through”. In this article I’m using the term “overlapping action” in a slightly different and expanded sense, one specific to writing CSS: how to create the impression of multiple independent actions from a single CSS animation directive.

The Perils of One-to-One

Quite understandably, beginners often assume that independent animated elements on a page must be moved using different animations, even if their motions are very similar: it’s often assumed that motions like those shown in the demo above would require five separate animations.

Inevitably this leads to a tremendous amount of code, repetition, and typing: the same problem we have when trying to write general CSS for related page components that are just a little bit different from each other.

The Upside of Separation

The solution is to create the correct animation for one element, then separate out the property that is different. For the animation we’re looking at above, the markup might look something like this:

<figure id="bouncy">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</figure>

And the initial CSS like this:

#bouncy {
  color: #223;
  min-height: 200px;
  display: flex;
  background: currentColor;
  border: 10px solid;
  align-items: flex-end;
  justify-content: space-between;
}
#bouncy div {
  width: 15%;
  height: 10px;
  background: red;
  margin-right: .5rem;
  transform-origin: center bottom;
}

A brief breakdown:

  1. color is used as a shortcut for both the border and background (via currentColor)
  2. The <div> elements are spaced apart with flexbox; align-items: flex-end ensures that they start at the bottom of the #bouncy container.
  3. By default elements are transformed from their computed center; changing the value of transform-origin ensures that any transformation will occur from the bottom of the element.
  4. Vendor prefixes are not included for the sake of brevity and clarity.

Adding Animation

To get one of the bars moving correctly, I’ll add an animation that will scale it upwards 20×:

@keyframes risenfall {
  to { transform: scaleY(20); }
}

Calling on this animation, I’ll address the first <div> element with an nth-child selector:

#bouncy div:nth-child(1) {
  animation: risenfall 1s infinite alternate cubic-bezier(.53,.14,.83,.67);
}

This will make the first <div> scale up and down smoothly; I’ve added a custom cubic-bezier function to give the animation a little “snap”. alternate ensures that the animation will “ping-pong” back and forth, while infinite effectively cycles the animation forever.

Applying this animation to all the <div> elements would, unsurprisingly, make them all move the same way, at the same time:

#bouncy div {
  animation: risenfall 1s infinite alternate cubic-bezier(.53,.14,.83,.67);
}

The result:

If we think about it, the only thing different about the motion of each of the bars in the final demo is that they start at a slight delay after each other. We have a property for that in CSS: animation-delay. By applying that property to each bar individually, we can create the appearance of complex animation. The first bar is fine, so we’ll just address the second through fifth:

#bouncy div:nth-child(2) {
  animation-delay: .3s;
}
#bouncy div:nth-child(3) {
  animation-delay: .4s;
}
#bouncy div:nth-child(4) {
  animation-delay: .5s;
}
#bouncy div:nth-child(5) {
  animation-delay: .6s;
}

Each bar is still influenced by the overall animation, but the individual delays cause them to look as if they are independent.

Conclusion

The more elements you have to animate (and the more individualized their actions) the more code you have to write, bringing us back to the initial problem; eventually, you will probably want to use a CSS preprocessor such as or to automate this process. However you approach the problem, the principle remains the same: group appearances / animation together when possible, and separate out the exceptions as small style rules.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/rVoJGz