The difference between transition and transition-group components in Vue

Animations seem to be a necessity these days in frontend development, improving the user experience in apps by a significant margin. Thankfully vue.js shipped two built-in components that abstract away the complexities of animating our apps; transition and transition-group.

In this article, we'll be looking at a brief overview of the similarities and differences between these two components, how to use them and possible gotchas when working with them.

First off, we look at the similarities:

Similarities

These two components as I mentioned earlier are used for animating between elements and components in our vue.js app.

  • Both components are wrapped around the elements or components to be animated.
  • Both components require a name attribute indicating the name of the animation to be applied.

Here's a high-level refresher on how to use these components:

<transition name="fade">
  <!-- elements to be animated -->
</transition>

<transition-group name="fade">
  <!-- elements to be animated -->
</transition-group>

That's just about how similar these components are, now its time to look at their differences.

Differences

The major difference between the transition and transition-group component is how they are used.

How to use the transition component

The transition component is used to animate elements or components that fall under these categories:

  1. Dynamic components
  2. Conditionally displayed components (v-show)
  3. Conditionally rendered components (v-if)
  4. Root nodes of components
  5. router-view components

Example with dynamic components

Below is an example of how you would use the transition component with dynamic components.

<template>
	<div>
    	<transition name='fade'>
    		<component :is="currentComponent"></component>
        </transition>
    </div>
</template>

<script>
import UserOne from '~/components/users/One.vue'
import UserTwo from '~/components/users/Two.vue'
export default{
	props: {
    	userId: {
        	type: Number,
            required: true
        }
    }
	components:{
    	UserOne,
        UserTwo
    },
    computed:{
    	currentComponent(){
        	return this.userId === 1? 'UserOne' : 'UserTwo'
        }
    }
}
</script>

In the snippet above, two components will be toggled based on the value of the userId prop, we use the transition component to animate the process.

Example with conditionally displayed components

<template>
	<div>
    	<button @click="showDescription = !showDecription">toggle description</button>
    	<transition name='bounce-up'>
    		<p v-show="showDescription">
            	This is a decription.
            </p>
        </transition>
    </div>
</template>

<script>
export default{
	data(){
    	return {
        	showDescription: false
        }
   	}
}
</script>

The example above uses the conditional display directive (v-show) to toggle the display of the element and we can now animate when the element appears and disappears.

Example with router-view component

If our app had one base component that was responsible for rendering all route components, usually (App.vue), we could wrap the router-view component with a transition component to animate when routes are being moved to and from.

<template>
	<div>
    	<nav>
        	<router-link to="/">Home</router-link>
            <router-link to="/about">About</router-link>
            <router-link to="/contact">Contact us</router-link>
        </nav>
        
       	<transition :name="transitionName">
        	<router-view></router-view>
		</transiton>
    </div>
</template>

<script>
export default {
    data(){
		return {
        	transitionName: 'fade-up'
		}
    }
	watch: {
    	'$route'(to, from){
            const toDepth = to.path.split('/').length
            const fromDepth = from.path.split('/').length
            this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
        }
    }
}
</script>

How to use the transition-group component

The transition-group component has a different use-case when dealing with animations.

transition-group is used when dealing with the entry and exit of multiple components or elements in the DOM (eg. rendering lists).

Before we get into get code examples, let's look at how transition-group differs from transition

  • An element is actually wrapped around all elements being rendered, by default its span but it can be changed with the tag attribute.
  • Elements inside the transition-group component must have a key attribute with unique values (using the indices of the element does not work, it is not unique).
  • The CSS animations are applied to the individual elements and not the wrapper element.
<template>
<div>
  <h2>Todos:</h2>
  <transition-group name="fade-x" tag="ol">
    <li v-for="todo in notDone" :key="todo.id">
      <label>
        <input type="checkbox"
          v-on:change="toggle(todo)"
          v-bind:checked="todo.done">

        <del v-if="todo.done">
          {{ todo.text }}
        </del>
        <span v-else>
          {{ todo.text }}
        </span>
      </label>
    </li>
    </transition-group>
</div>
</template>

<script>
export default {
  data(){
    return{
      todos: [
        { id: 1, text: "Learn JavaScript", done: false },
        { id: 2, text: "Learn Vue", done: false },
        { id: 3, text: "Play around in JSFiddle", done: true },
        { id: 4, text: "Build something awesome", done: false }
      ]
    }
  },
  methods: {
  	toggle: function(todo){
    	todo.done = !todo.done
    }
  }, 
  computed: {
  	notDone(){
    	return this.todos.filter(todo=> !todo.done)
    }
  }
}
</script>

<style>
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

del {
  color: rgba(0, 0, 0, 0.3);
}

.fade-x-enter-active, .fade-x-leave-active{
   transition: all .3s;
}

.fade-x-enter, .fade-x-leave-to{
  opacity: 0;
  transform: translateX(20px)
}
</style>

Now if a task is checked off, you can notice the animation before the element leaves the DOM.

You can create a new fiddle here and paste the snippet above to test it out.

TLDR

If you didn't have time to stick around long enough to read the entire article, here's a summary:

  • Use transition when only one element or component will be rendered at a time, and transiton-group for multiple.
  • transition-group elements must have unique key attributes.
  • Use the tag attribute on the transition-group component to change the kind of element that wraps all elements within.

Found the article helpful?

Subscribe to my Newsletter to receive new articles in your inbox 👇

No spamming, just fresh articles straight out of the oven.