Transition Name & Mode

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

As we just learned, Vue adds, these v- classes to our element when it's being hidden or shown.

We actually have partial control over how these are named. In the template, on the transition element, add name="fade". What does that do? Very simply: it changes the prefix that Vue uses for the classes.

<template>
<div :class="[$style.component, 'container-fluid']">
<div class="row">
... lines 4 - 18
<div class="col-xs-12 col-lg-9">
... lines 20 - 21
<div class="content p-3">
... lines 23 - 49
<transition name="fade">
... lines 51 - 55
</transition>
</div>
</div>
</div>
</div>
</template>
... lines 62 - 178

Thanks to this, down in the styles, instead of v-, everything needs to be fade-.

We could have used name="" anything. Fade is a good name because the CSS causes the element to, ya know, fade in and out.

... lines 1 - 161
<style lang="scss" module>
... lines 163 - 164
.component :global {
... lines 166 - 169
.fade-enter-active, .fade-leave-active {
transition: opacity 3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
}
</style>

Move over and try it. Yep! It works like before, except that the classes are different. The advantage of this is that you could have CSS in your project for several different types of transitions, like fade or bounce and then choose whichever one you want with <transition name="".

Transitioning Between 2 Components

Now that we are Vue transition pros, up in the template, let's remove that temporary element. Our real goal is to transition between the shopping cart and checkout components so that it's not so... abrupt.

No problem! Wrap each component in its own transition component, right? We could do that - and I'll talk more about that approach in a few minutes. But we can also wrap a transition around multiple components or elements.

Add <transition name="fade" so that we use the same CSS, wrap this around both components and indent them.

<template>
<div :class="[$style.component, 'container-fluid']">
<div class="row">
... lines 4 - 18
<div class="col-xs-12 col-lg-9">
... lines 20 - 21
<div class="content p-3">
... lines 23 - 24
<transition name="fade">
<shopping-cart-list
v-if="completeCart && currentState === 'cart'"
:items="completeCart.items"
@updateQuantity="updateQuantity"
@removeFromCart="removeProductFromCart(
$event.productId,
$event.colorId,
)"
/>
<checkout-form
v-if="completeCart && currentState === 'checkout'"
/>
</transition>
... lines 40 - 50
</div>
</div>
</div>
</div>
</template>
... lines 56 - 172

Wrapping multiple elements or components inside a single transition only works when you're using v-if, not v-show... and it only works when there's exactly one element or component being displayed at a time... which is exactly our situation! I'll talk more about this limitation in a few minutes.

But let's try it! And... yea! It's super slow, but it works! Let's shorten that transition time to something more realistic. Down at the bottom of the component, change the 3 seconds to .2 seconds.

... lines 1 - 155
<style lang="scss" module>
... lines 157 - 158
.component :global {
... lines 160 - 163
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
}
... lines 167 - 169
}
</style>

Transition Modes

Try it now. Much better. Though... it's still kinda jumpy. The problem is that the new component is already being shown and fading in while the old component is still there and fading out. So it... kind of jumps a bit, which will look worse once the checkout component renders a form.

Fortunately, because we wrapped both of these components inside the same transition, we can leverage a cool mode option on the transition. Say mode="out-in":

<template>
<div :class="[$style.component, 'container-fluid']">
<div class="row">
... lines 4 - 18
<div class="col-xs-12 col-lg-9">
... lines 20 - 21
<div class="content p-3">
... lines 23 - 24
<transition
name="fade"
mode="out-in"
>
... lines 29 - 41
</transition>
... lines 43 - 53
</div>
</div>
</div>
</div>
</template>
... lines 59 - 175

This says:

Fade out the old component and then fade in the new one... instead of doing them at the same time.

Check it out now... awesome! The old component fades out completely before the new one starts: no jumping around.

The Problem with Transitions and v-if

But... there is one problem with the approach of wrapping multiple elements or components inside the same transition. The problem is v-if. It's not very obvious right now, but each time we click the button, the old component is completely destroyed. When we click back, it's completely re-created. That could be a slight performance issue, though that's not really the problem. The problem is that any data on the component is completely lost.

This will be much more obvious in a few minutes when we add a true checkout form. Then, if we filled out a few fields... then clicked back to the cart... then returned to the checkout form, those fields would now be blank because the component was re-created with new data and new HTML.

The solution would be to make how we hide & show the components smarter. We would still rely on v-if to check for completeCart... because we can't render at all until that variable is ready. But then we would use v-show to check the current state. Thanks to that, instead of destroying the component, it would just hide it.

The problem is that... this doesn't work:

<transition> can only be used on a single element. Use <transition-group> for lists, which is not our situation.

So basically... we can't wrap multiple things unless we're using v-if exclusively. The solution would be to wrap each component in its own transition tag: one around the shopping cart and another around the checkout form.

But when we do this, we can no longer use the mode option, which is what helped us fix that jumping problem. One common work-around is to absolutely position the two components on top of each other so that as they fade out and in, the do it in the same spot without the jumping.

Anyways, I'm going to undo this and go back to v-if: I just wanted to give you some extra background. I'll refresh to make sure I didn't break things... excellent!

There's one more thing that I want to transition: the title on the page. If you watch, it still changes instantly. That's not a huge deal, but it would be cool if that also faded out and faded back in. This is interesting because, in this case, we will be transitioning over a prop change. Let's talk about how to do that next.

Leave a comment!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.3
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.2.0
        "eslint-plugin-import": "^2.19.1", // 2.22.1
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.12
        "vue-loader": "^15.9.1", // 15.9.4
        "vue-template-compiler": "^2.6.11", // 2.6.12
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}