Animating multiple elements with Vue transitions (and CSS var() function)
Tags: css, transition, var, vue
Published: 2021-04-29 18:06:49

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/633se7z9bj4xtgajrod2.gif)

[TLDR: CodePen link](https://codepen.io/ondrabus/pen/GRraYoo)

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:

```js
data(){
  return {
    bars: [75, 50, 100]
  }
}
```

And they are rendered in a for loop:

```js
<div v-for="(bar, i) in bars" :key="i" class="bar">{{bar}}%</div>
```

## Animating the bars using <transition>

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

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

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:

```js
<div style="--variableName: variableValue"></div>
```

And use `var(variableName)` in the actual stylesheet:

```js
.selector {
  CSS property: var(--variableName);
}
```

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

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

And add the accompanying styles:

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

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

.bars-enter-active .bar {
  transition: all 2s;
  transition-delay: var(--delay);
}
```

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:

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

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.](https://codepen.io/ondrabus/pen/GRraYoo)

# 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](https://bit.ly/kontent-discord) and ping me a message 😊