Staggered Animations with CSS Custom Properties

Staggered Animations with CSS Custom Properties

At 4/19/2024

A series of arrows with staggered positions and opacity, over a colored background.

Movement in nature doesn’t happen all at once. Imagine a flock of birds taking off, raindrops splashing on the ground, or trees bending in the wind. The magic of these moments comes from many small movements overlapping and converging. I wanted to bring this natural movement into my web animations.

This type of animation is called staggered animation. A few years ago my colleague Tyler Sticka wrote a great article on staggering animations with JavaScript and GSAP. His article describes a really cool animation pattern he pulled off with JavaScript, but my needs were simpler, and I wanted to use CSS transitions instead of GSAP.

I was designing mobile navigation for a website. When a user clicked a link I wanted to slide in a list of links with a staggered animation.

First, I set up the animation without any staggering:

.Menu__link {
  opacity: 0;
  transform: translate(100%, -300%);
  transition-duration: 0.2s;
  transition-timing-function: ease-out;
  transition-property: opacity, transform;
}

.Menu.is-open .Menu__link {
  opacity: 1;
  transform: translate(0);
}
Code language: CSS (css)

My first attempt at adding staggering used :nth-child() and transition-delay. This allowed me to target each link and time its transition individually:

.Menu__link:nth-of-type(2) {
  transition-delay: 0.025s;
}
.Menu__link:nth-of-type(3) {
  transition-delay: calc(0.025s * 2);
}
.Menu__link:nth-of-type(4) {
  transition-delay: calc(0.025s * 3);
}
.Menu__link:nth-of-type(5) {
  transition-delay: calc(0.025s * 4);
}
.Menu__link:nth-of-type(6) {
  transition-delay: calc(0.025s * 5);
}
Code language: CSS (css)

This worked, but I had to write a lot of CSS to target each link. My CSS supported 6 links, but what if the menu grew to 7? Or 8? Would I just keep on adding :nth-child() rules? Should I do that now to make sure this doesn’t break in the future? How many links should I support? 10? 15? 20? I figured there had to be a better way…

Custom properties allow you to use variables in your CSS code, and these variables can have different values in different scopes. We can even set custom properties in HTML style attributes, allowing us to write a concise CSS snippet that will work with an infinitely large list of links!

<li class="Menu__item">
  <a href="#" class="Menu__link" style="--index: 0;">Babar</a>
</li>
<li class="Menu__item">
  <a href="#" class="Menu__link" style="--index: 1;">Dumbo</a>
</li>
<li class="Menu__item">
  <a href="#" class="Menu__link" style="--index: 2;">Echo</a>
</li>
<!-- etc. -->
Code language: HTML, XML (xml)
.Menu__link {
  --index: 0;
  transition-delay: calc(0.025s * var(--index));
}
Code language: CSS (css)

When hiding the menu I wanted to reverse the animation. I could do this by removing the is-open class, but there was a problem. The menu should animate in the opposite order when being hidden. The first link should be the first to be shown, but the last to be hidden. In order to do this I needed to swap my transition delays.

I was able to achieve this using another custom property and some calc statements. First off, I added a new custom property that matched the number of links in my list. Since custom properties are inherited by child elements, I set this once on the list wrapper, instead of setting it on every link:

<ul style="--length: 6">
  <!-- Your list items and links -->
</ul>
Code language: HTML, XML (xml)

We can then do some arithmetic to calculate different durations depending on whether we’re showing or hiding elements:

.Menu__link {
  /* This delay will take effect when _hiding_ elements */
  transition-delay: calc(0.025s * (var(--length) - (var(--index) + 1)));
}

.Menu.is-open .Menu__link {
  /* This delay will take effect when _showing_ elements */
  transition-delay: calc(0.025s * var(--index));
}
Code language: CSS (css)

With those changes in place we’ve reversed our animation when hiding elements! The first link will be the first to show, but the last to be hidden.

The example above always animates in the same order. Part of what made Tyler’s previous explorations special was that the animation order varied depending on which link you clicked in the open menu. I showed an early draft of this article to Tyler and he whipped up an awesome proof of concept, showing how the two ideas could be combined to recreate a similar effect with custom properties.

I like staggering transitions with custom properties, but it’s important to be aware of some downsides and caveats.

For this to work we need to set these custom properties in our HTML. If we’re directly editing HTML this can get messy. That said, I’m generally not writing HTML directly.

Usually I’ll be using a templating language like Handlebars, or a framework like Vue. Here’s an example of how you could set this up in Vue:

<ul :style="`--length: ${elephants.length}`">
  <li v-for="(elephant, index) in elephants" :key="elephant.id">
    <a href="elephant.url" class="Menu__link" :style="`--index: ${index};`">
      {{ elephant.name }}
    </a>
  </li>
</ul>
Code language: HTML, XML (xml)

Some developers may cringe at the lack of separation of concerns here, since we’re moving some of our styling from CSS to HTML. This solution may not work for everyone, or for every project, but in my case this tradeoff was worth it.

Another thing to be aware of is that older browsers like IE11 may not support custom properties. The good news is that old browsers will ignore the transition-delay rule they don’t understand, so they’ll simply show an un-staggered transition. That said, if a large percentage of your users are still on older browsers, you may want to use the :nth-child() option.

We’ll also need to properly add and remove the hidden attribute and account for users who prefer reduced motion to make sure our animations are accessible.

By staggering our animation we’ve designed a more natural, organic feeling interaction. This is one of many ways staggered animations can be used to enhance our digital experiences. I’m excited to keep exploring and pushing the boundaries of animation on the web!

Copyrights

We respect the property rights of others and are always careful not to infringe on their rights, so authors and publishing houses have the right to demand that an article or book download link be removed from the site. If you find an article or book of yours and do not agree to the posting of a download link, or you have a suggestion or complaint, write to us through the Contact Us, or by email at: support@freewsad.com.

More About us