Animating an element when showing or hiding it is pretty easy in Vue — you just have to wrap the element with the <transition>
component.
But what about those cases when you want to show or hide nested children sequentially? For example, after the root element is shown, show element A, and after that, show element B, and so on.
This is still an easy thing to do in Vue; you just need a way to know when the previous transition is done to start the next one.
If you haven’t done it before, and you’re wondering how, I’m going to save you some time and show you how to do it in a clean, controllable way. But before that, take a look at this CodePen to see what we’re going to build:
As you can see in the demo above, we’re going to create a simple modal box that’s displayed in two steps (transitions). First, we show the overlay background, and then we show the white content box.
I’ll break the tutorial into three sections. First, we’ll create the button and the modal box. The user can show the modal box by clicking on the button and close it by clicking on the overlay background. In this section, the modal will open without animations.
In the second section, we’ll add a single-step transition — so the overlay background and the content box will be shown simultaneously.
And in the final section, we’ll add a nested transition for the content box — which will be shown after the overlay background transition is done.
Showing the modal box without animation
Let’s start things quickly with Vue CLI 3’s instant prototyping. So create App.vue
, and put the following into the <template>
section:
<template>
<div id="app">
<modal v-if="showModal" @close="showModal = false" />
<button class="button" @click="showModal = true">Show Modal</button>
</div>
</template>
We have here a button that sets showModal
to true
. And when it’s true, we display the <modal>
component, as shown above. (Note that we haven’t created that component yet, but we will shortly.)
Also, note how we set showModal
to false
when <modal>
emits close
custom event.
Now, in the <script>
section, put this:
<script>
import Modal from './Modal'
export default {
components: { Modal },
data () {
return {
showModal: false
}
}
}
</script>
And then this into <style>
:
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.button {
border-radius: 2px;
background: #D55672;
border: none;
padding: 10px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
color: #FFF;
outline: none;
transition: 0.1s background;
}
.button:hover {
background: #AA445B;
}
</style>
Next, let’s create the Modal.vue
component, then add the following into the template section:
<template>
<div class="modal" @click="$emit('close')">
<div class="content" @click.stop>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quisquam,
placeat, unde! Architecto laboriosam ducimus atque cum dolore doloribus
obcaecati vero. Minus porro sapiente unde fuga incidunt quidem
necessitatibus mollitia libero?
</div>
</div>
</template>
Note that the root element here (.modal
) is used as the overlay background. When the user clicks on it, it emits the close
event.
Also, note how we’re using @click.stop
on .content
to prevent it from closing the modal when it’s clicked on.
The <script>
section should be empty for now:
<script>export default {}</script>
Next, add this for the styling:
<style scoped>
.modal {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.6)
}
.content {
position: absolute;
top: 50%;
left: 50%;
width: calc(100% - 20px);
max-width: 500px;
transform: translate(-50%, -50%);
background: #FFF;
border-radius: 3px;
padding: 20px;
line-height: 1.5;
font-size: 18px;
color: #444;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
</style>
At this point, you should be able to open/close the modal box, but without animation.
Single-step transition
Now, let’s make the modal box open with a single-step fade transition.
It’s so easy to do. Just wrap the content of the modal component with <transition name="fade"></transition>
, like this:
<template>
<transition name="fade">
<div class="modal" @click="$emit('close')">
<div class="content" @click.stop>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quisquam,
placeat, unde! Architecto laboriosam ducimus atque cum dolore doloribus
obcaecati vero. Minus porro sapiente unde fuga incidunt quidem
necessitatibus mollitia libero?
</div>
</div>
</transition>
</template>
Then, define the fade transition in the <style>
section, like this:
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: 0.2s opacity ease-out;
}
That’s it! Now your single-step transition should work as expected.
Applying nested transitions to the modal
Here’s how we’re going to do it:
- Wrap
.content
with<transition name="fade">
so it can be animated. - Add
v-if="showContent"
to.content
so we can specify when that element can be shown (we can do that by settingshowContent
totrue
). Also, note that we have to defineshowContent
in the modaldata()
object. - Listen for
@after-enter
on the root<transition>
component. And when that event fires, we setshowContent
totrue
. - Modify
@click
of.modal
to setshowContent
tofalse
. So, instead of emittingclose
event immediately on click, we hide the.content
element with animation, and only after that animation is done, will we emit theclose
event. So this leads us to our next point. - Add
@after-leave="$emit('close')"
to.content
<transition>
component.
After applying the above steps, the modal’s <template>
should become like this:
<template>
<transition name="fade" @after-enter="showContent = true">
<div class="modal" @click="showContent = false">
<transition name="fade" @after-leave="$emit('close')">
<div v-if="showContent" class="content" @click.stop>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quisquam,
placeat, unde! Architecto laboriosam ducimus atque cum dolore
doloribus obcaecati vero. Minus porro sapiente unde fuga incidunt
quidem necessitatibus mollitia libero?
</div>
</transition>
</div>
</transition>
</template>
And let’s not forget to add showContent
to data()
object:
<script>
export default {
data () {
return {
showContent: false
}
}
}
</script>
So, here’s how the showing part works: When the user clicks on the button, we set showModal
to true
, and that triggers the root <transition>
, which only shows the overlay background alone. After that transition is done, after-enter
is fired. And on that event, we set showContent
to true
to start the transition for that element.
Now for the hiding part, when the user clicks on the overlay background, we set showContent
to false
, which runs the leaving transition for the .content
element. And when that transition is done, it fires the after-leave
event. We handle that event by emitting the close
event which sets showModal
to false
to hide modal box with transition.
If you now run the example, you should see the nested transitions working as expected.