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:
- Dynamic components
- Conditionally displayed components (
v-show
) - Conditionally rendered components (
v-if
) - Root nodes of components
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 thetag
attribute. - Elements inside the
transition-group
component must have akey
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, andtransiton-group
for multiple. transition-group
elements must have unique key attributes.- Use the
tag
attribute on thetransition-group
component to change the kind of element that wraps all elements within.