Animated transitions in lists and tables with VueJS10 min read

Designer’s Desk

VueJS ships with easy ways to integrate transitions between elements. Here, we are studying the particular case of lists and tables.

Without transitions, user experience is mediocre

In this article, we are going to work with a list of fruits displayed as a list and as a table. Indeed, setting up is slightly different in both cases.

This code, which will serve as a base for this article, does not show any transition. We can very well understand that a user may experience some difficulty in tracking what was changed every time the data is modified. I’m sure that the most curious of you went right into the code to see more clearly into it. Animated transitions are obviously very useful.

Moving elements inside a list

Animations I find the most spectacular is the ones that move elements inside a list or a table. And paradoxically, it’s the easiest to set up. In a simple table of data presentation, it can be enough to animate column sorting, or filtering for example.

VueJS provides us with a component named transition-group. This component is specifically made to animate lists of elements. It then contains for sure a v-for.

Transitions inside a list

The transition-group component has a tag attribute. This attribute allows us to fill out the type of tag that will actually be rendered in the DOM. Indeed, the transition-group tag does not exist in HTML. It is specific to VueJS.

In our list example, the transition-groupcomponent takes the place of the ul tag. So we give the ul value to the tag attribute.

The transition-groupcomponent also has a name attribute. The value of this attribute will be used as a root to define animations in CSS.

<ul class="list-group">
  <li class="list-group-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</ul>

Then becomes:

<transition-group class="list-group" name="fruit-list" tag="ul">
  <li class="list-group-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</transition-group>

At this stage, the moving animation of elements in the list is operational. But we do not have specified any duration for this animation yet. So we cannot witness the transition animation. The duration is defined in CSS. And that is actually where the nameattribute of the transition-groupwill be useful. VueJS will give the <name>-move class to every moving element during the animation, where <name> is the value of the name attribute.

Here, for example, we wish that the move animation has a duration of 1 sec:

.fruit-list-move {
  transition: transform 1s;
}

Transition in a table

Regarding tables, it should be the tbodytag that would, in theory, be replaced by a transition-group one. Yet if we do, the animation won’t work. That’s because of DOM template parsing that must absolutely find a tbody tag inside a tabletag.

It is then mandatory to dynamically bind a transition-group to the tbody using the is special attribute. What-the-hell-does-that-mean ? Nothing too complicated, see for yourself:

<table class="table">
  [...]
  <tbody>
    <tr v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

Becomes:

<table class="table">
  [...]
  <tbody name="fruit-table" is="transition-group">
    <tr v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

And as for the list, we need to add a rule in CSS:

.fruit-table-move {
  transition: transform 1s;
}

Full result with VueJS

Here is how it goes:

Pretty cool, right! Even the addition of a new element is kinda animated, if we overlook the “downward jump” the tag that follows (Number of items) is experiencing. And that is exactly what we are going to solve in the next chapter of this article.


Adding an element to a list

To fix the “jump” issue we were just talking about, let’s see what happens in the current situation:

  1. The “Kiwis” element appears. It is actually fully present, just hidden behind the elements that follow it.
  2. The following elements are moved downwards. They are then moving, and take a full second to reach their new position.

We have the feeling that the “Kiwis” element appears gradually, but in fact it well arrives abruptly and is just gradually “un-hidden” by the elements of the list that follow it while they reach their final position. It’s this sudden appearing that is betrayed by the “jump” of the tag that follows our list.

To fix this unwanted behavior we are gonna have to affect the height of the element added to the list. As a matter of fact, we will modulate it’s height with an animation.

Enter transition

VueJS automatically applies classes to elements (tags) that appear or disappear from the DOM. These classes are dynamically applied at various stages of the enter transition:

  • The v-enter class is briefly applied before the element is inserted. It gives the animation startup state.
  • The v-enter-to class is applied as soon as the element is inserted, as soon as v-enter is removed. It represents the target state of the transition’s end, i.e. the state we want the element to reach at the end of the transition.
  • The v-enter-active class is applied during the entire transition. It is applied when v-enter is applied, and it is removed when v-enter-to is removed.
Image for post

Juggling with this 3 classes will enable us to configure the enter transition animation of an element in a list or a table.

Appearing of an element in a list

In reality, the classes that are applied to our appearing element are not named v-enter, v-enter-to, and v-enter-active. Those are suffixes. Indeed, the name of the transition classes is composed of the name given to the transition-group element, followed by one of those names.

This way, for our list, the classes automatically attributed by VueJS will be fruit-list-enter, fruit-list-enter-to, and fruit-list-enter-active.

In relation to the moving animations of the previous chapter, we have to make some adjustments. Indeed, our transitions do not concern only moves anymore, but also appearing. And also disappearing but we will learn about that in the next chapter.

We are then gonna replace:

.fruit-list-move {
  transition: transform 1s;
}

By:

.fruit-list-item {
  transition: all 1s;
}

And add the fruit-list-item class to the elements of the list:

<transition-group class="list-group" name="fruit-list" tag="ul">
  <li class="list-group-item fruit-list-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</transition-group>

This way, every transition applied individually to the elements of the list will have a duration of 1 second. The moving animations are kept unmodified. And the entering and leaving transitions will also have a duration of 1 second.

Let’s now give the beginning and ending states of the appearing transition. We want the element to have a null height at the beginning of the animation, and grow to reach its final height at the end of the transition.

Yet we cannot know the height of a div in advance. It is computed based on the height of the elements it contains. On the other hand we can set a maximal height that we do not want it to exceed, thanks to the max-height attribute. So for the animation end, we only need to decide of a maximal height that we know we won’t ever reach. But we need to stay quite close to the actual value because the animation speed will be calculated upon this target, and it could become too fast if this target is too far from the real value.

At the beginning of the animation, we want a height of 0px. We then set the max-height attribute to 0, but this won’t be enough. Indeed, our div has some padding. It’s padding-top and padding-bottom attributes are not null by default. And they will prevent our div to fully disappear. The solution to this problem is pretty simple though, we only have to also set a value of 0 to those two attributes at the beginning of the animation (.fruit-list-enter). It is not necessary to set a value for the end of the animation (.fruit-list-enter-to) because as soon as the .fruit-list-enter class is removed, the padding will have its default value as a target.

Here is the full CSS we need to write for the appearing animation of an element in a list:

.fruit-list-item {
transition: all 1s;
}
.fruit-list-enter {
max-height: 0px !important;
padding-top: 0px !important;
padding-bottom: 0px !important;
}
.fruit-list-enter-to {
max-height: 80px;
}

Notice the!important instruction at the end of some lines. It is to make sure this rule will not be overwritten by any other.

Appearing of a line in a table

The same principle is applied to tables, except for one detail: we cannot force the height of a table’s line using its max-height attribute.

As for lists, let’s start by adding a class to the lines of our table. This way:

.fruit-table-move {
  transition: transform 1s;
}

Becomes:

.fruit-table-item {
  transition: all 1s;
}

And we edit the tr tag:

<table class="table mb-0">
  [...]
  <tbody name="fruit-table" is="transition-group">
    <tr class="fruit-table-item" v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

So you got it: CSS for transitions in a table will be a bit different from CSS for transitions in a list. Indeed, the height of a tr tag depends, like for a div, of its content. But unlike a div, it has no power over it. We are going to have to act on the lines content as well:

.fruit-table-item {
  transition: all 1s;
}
.fruit-table-item > * {
  transition: all 1s;
  overflow: hidden;
}
.fruit-table-enter {
  line-height: 0 !important;
}
.fruit-table-enter > * {
  padding-top: 0px !important;
  padding-bottom: 0px !important;
}

The height of a table’s lines is set by the line-height attribute.

The > * CSS selector, used twice in this code extract, enables us to select every child of the tr tag. i.e. every tag it contains. We use this selector to set a padding-top and padding-bottom to 0 for every line of text.

And here is the result:

This example is quite simple because the lines of the table only contain text. If they contained buttons or pictures, setting up transitions with VueJS would be a bit more painful.


Removing an element from a list

We now reach the last step of this article’s subject: animation of an element disappearing from a list or a table.

As with appearing, VueJS automatically applies classes to elements leaving the DOM. Those classes are applied at different stages of the leave transition:

  • The v-leave class is briefly applied (one frame) before the element starts the leave transition. It gives the animation startup state.
  • The v-leave-to class is applied as soon as the element starts leaving, as soon as v-leave is removed. It represents the target state of the transition’s end, i.e. the state we want the element to reach at the end of the transition.
  • The v-leave-active class is applied during the entire transition. It is applied when v-leave is applied, and it is removed when v-leave-to is removed.
Image for post

Disappearing of an element from a list or a table

Nothing magic here. The transition we want to build is the exact opposite of the appearing transition we just put into place. The line will shrink in height until it’s gone. Therefore the setup is exactly the same. We only need to add the proper classes to the CSS:

.fruit-list-item {
  transition: all 1s;
}
.fruit-list-enter,
.fruit-list-leave-to {
  max-height: 0px;
  padding-top: 0px !important;
  padding-bottom: 0px !important;
  overflow: hidden;
}
.fruit-list-enter-to,
.fruit-list-leave {
  max-height: 80px;
}

.fruit-table-item {
  transition: all 1s;
}
.fruit-table-item > * {
  transition: all 1s;
  overflow: hidden;
}
.fruit-table-enter,
.fruit-table-leave-to {
  line-height: 0;
}
.fruit-table-enter > *,
.fruit-table-leave-to > * {
  padding-top: 0px !important;
  padding-bottom: 0px !important;
}

And the final result looks like this:

There you go! You now know everything I know.

To go a little further

As I’m not making anything up, I’ll give up my sources. Or in this particular case: my source. The VueJS documentation, at the transitions chapter : https://vuejs.org/v2/guide/transitions.html

Do not hesitate to play around with the JSFiddle I provided in this article, our make your own ones. It’s the best way to understand and learn.

Related Posts

Leave a Reply