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
nameattribute 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:
- Dynamic components
- Conditionally displayed components (
v-show) - Conditionally rendered components (
v-if) - Root nodes of components
router-viewcomponents
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
spanbut it can be changed with thetagattribute. - Elements inside the
transition-groupcomponent must have akeyattribute 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
transitionwhen only one element or component will be rendered at a time, andtransiton-groupfor multiple. transition-groupelements must have unique key attributes.- Use the
tagattribute on thetransition-groupcomponent to change the kind of element that wraps all elements within.
