DEV Community

Cover image for Animating multiple elements with Vue transitions (and CSS var() function)
Ondrej Polesny for Kontent by Kentico

Posted on

Animating multiple elements with Vue transitions (and CSS var() function)

Transitions are a great way to make your app interactive. Vue.js allows us to animate any dynamic events on elements with the <transition> component. But what if you need to animate multiple elements that are related to each other?

Bars animation

TLDR: CodePen link

I stumbled upon this problem when I wanted to animate graph bars. Each bar needed to have a specific width and had to be animated right after the previous one. Also, I did not want to hardcode the number of bars, so it all needed to be data-driven.

Building the data and template

So let's isolate the problem, this is how the bars are defined in a Vue app:

data(){
  return {
    bars: [75, 50, 100]
  }
}
Enter fullscreen mode Exit fullscreen mode

And they are rendered in a for loop:

<div v-for="(bar, i) in bars" :key="i" class="bar">{{bar}}%</div>
Enter fullscreen mode Exit fullscreen mode

Animating the bars using

The first step in animating them is to add a component and a single root as that's what Vue requires:

<transition name="bars" appear>
  <div>
    <div v-for="(bar, i) in bars" :key="i" class="bar">{{bar}}%</div>
  </div>
</transition>
Enter fullscreen mode Exit fullscreen mode

Note the appear attribute. It instructs Vue to animate the first (and only) child element when it appears in the DOM. Vue is clever enough to search the applied styles for transition and it will add a few special classes to the element for the duration of the transition:

  • bars-enter-active for the whole duration
  • bars-enter-from at the start to define CSS to animate from, e.g. width: 0
  • bars-enter-to at the end to define CSS to animate to, e.g. width: 75%

The problem is, we need to define these properties on a more granular level - for each bar.

Connecting data with CSS using var()

The CSS var() function allows us to nicely connect dynamic data with CSS. We configure the data just like any other style definition of the element:

<div style="--variableName: variableValue"></div>
Enter fullscreen mode Exit fullscreen mode

And use var(variableName) in the actual stylesheet:

.selector {
  CSS property: var(--variableName);
}
Enter fullscreen mode Exit fullscreen mode

In this case, we'll need each bar to define its width and calculate the animation delay using the item's index:

<div v-for="(bar, i) in bars" :key="i" class="bar" :style="`--width: ${bar}%; --delay: ${i}s`">{{bar}}%</div>
Enter fullscreen mode Exit fullscreen mode

And add the accompanying styles:

.bar {
  width: var(--width);
}

.bars-enter-from .bar {
  width: 0;
}

.bars-enter-active .bar {
  transition: all 2s;
  transition-delay: var(--delay);
}
Enter fullscreen mode Exit fullscreen mode

The last thing to keep in mind is that Vue will only keep the special CSS classes on the root element for the amount of time defined in its transition. So let's add the expected animation time as transition duration:

<div style="`transition: all ${(bars.length-1)+2}s`">
Enter fullscreen mode Exit fullscreen mode

The animation will take exactly N+2 seconds if N is the number of bars and each bar takes 2s to render. If you set it to a lower value than needed, the animation of some bars will not finish and just skip to the final position.

See the functional sample on CodePen.

Conclusion

The combination of --variableName: variableValue and var(--variableName) is a nice way to avoid generating too many style definitions (that are even then limited) through for loops.

If you want to share your experience or get in touch, join the Kontent Discord and ping me a message 😊

Discussion (0)