Image

animating-vue

9 min read
Last update: December 19, 2021

JavaScript Hooks + Velocity

So far we’ve been learning about Vue’s transition and transition-group components, which provide us the necessary classes to build out enter and leave transitions. However, when our needs become a bit more unique or complex, we’ll need to start using JavaScript to build out our animations.

In this lesson, we’ll look at Vue’s JavaScript Hooks and combine that with a helpful animation library called Velocity.js to build out some more powerful animations.


JavaScript Hooks

So what are JavaScript Hooks? They are similar to Vue’s lifecycle hooks (beforeCreate, created, etc), but are specific to Vue’s transition and transition-group components. You can think of them as the stages of the lifetime of a Vue transition.

Below is a list of them

  • before-enter
  • enter
  • after-enter
  • before-leave
  • enter-cancelled
  • leave
  • after-leave
  • leave-cancelled

As you can see, there are hooks that are called during both the enter and leave transitions. When they are called, we can have them trigger methods, which may contain more complex behavior using JavaScript that we couldn’t have accomplished with just CSS alone.

<transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter" @enter-cancelled="enterCancelled" @before-leave="beforeLeave" @leave="leave" @after-leave="afterLeave" @leave-cancelled="leaveCancelled">
    <!-- ... -->
</transition>

Now that we understand what hooks are available to us, let’s build our first JavaScript-powered Vue transition.


A Drawer Transition

A common feature in a web app is a drawer-type component, which slides out to reveal something such as a user profile, a navigational menu, or dashboard. Inside of our starting code, you’ll see we now have a Drawer.vue file, which contains a user profile that pops on and off the screen with a button click.

Using the help of JavaScript hooks and the Velocity animation library, we’ll turn this into a “drawer” that slides out from the left side of the browser. But first, let’s examine the starting code.

📃 Drawer.vue

<template>
    <div>
        <button @click="isOpen = !isOpen">
            My Profile
        </button>

        <div v-if="isOpen" class="drawer">
            <img src="../assets/avatar.png" alt="avatar" />
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>

    </div>
</template>

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

The template has our button that toggles the isOpen data boolean, which determines if we’re displaying our drawer or not. And below that, we’ve got some scoped styles.

<style scoped>
    img {
        height: 2.5em;
        width: 2.5em;
        border-radius: 50%;
    }

    .drawer {
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 12em;
        height: 20em;
        border-radius: 1%;
        background-color: #e0e0e0;
        box-shadow: 0.08em 0.03em 0.4em #ababab;
        padding-top: 0.7em;
    }

    .drawer div {
        height: 3.5em;
        width: 95%;
        margin-top: 0.6em;
        background-color: #f0f0f0;
        border: 0.02em solid #ababab;
        border-radius: 1%;
    }
</style>

Our first step is to wrap the drawer in the transition component, and add the hooks we’ll be using.

📃 Drawer.vue

<template>
    <div>
        <button @click="isOpen = !isOpen">
            My Profile
        </button>

        <transition @before-enter="beforeEnter" @enter="enter" @leave="leave" :css="false">
            <div v-if="isOpen" class="drawer">
                <img src="../assets/avatar.png" alt="avatar" />
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </transition>
    </div>
</template>

As you’ll see, we’ll only need before-enter(), enter() and leave(), each of which will trigger a method by the same name. But what do we want these methods to do?

As for beforeEnter(), we want that method to set the initial style of the drawer before it enters the interface.

We’ll use the enter() method to set the styles we want the drawer to end up with when it enters.

The leave() method will set the styles we want the drawer to transition to when it leaves our interface.

Note: We’re using :css="false" to tell Vue it doesn’t need to worry about handling the transition classes here since we’re relying on the JavaScript hooks instead.

Before we write out these methods, we’ll import Velocity. If you look inside the package.json, you’ll see this library is already installed in our project (I did this for you - you’d just run npm i velocity-animate --save-prod to do so yourself).

📃 Drawer.vue

<script>
    import Velocity from 'velocity-animate'

    export default {
        data() {
            return {
                isOpen: false
            }
        },
        methods: {
            beforeEnter(el) {
                // we'll place our starting style here
            },
            enter(el, done) {
                // style to transition to on enter
            },
            leave(el, done) {
                // style to transition away to on leave
            }
        }
    }
</script>

Notice how our methods each have the el parameter. This will give our methods access to the element that is being transitioned, in this case our drawer div.

So how do we want our drawer to start out as? Well, we don’t want it to be visible, so it’ll need to be opacity: 0, and if it has width: 0 we can then expand it outward by transitioning it’s width. Let’s add those styles into beforeEnter()

Starting Style

📃 Drawer.vue

beforeEnter(el) {
el.style.opacity = 0
el.style.width = '0em'
},

Now in enter(), we can tell this method to turn our drawer visible and give it a width. We’ll use the Velocity() method to animate our opacity and width.

Enter Style

📃 Drawer.vue

enter(el, done) {
Velocity(
el, // element to animate
{ opacity: 1, width: '12em' }, // new style rules
{ duration: 1000, easing: 'easeOutCubic', complete: done } // define how transition happens and complete it
)
}

Let’s break down what is happening here. We’ve passed in a few arguments to the Velocity() method, first handing it the element to animate (el) and then giving it an object with the new style rules we want applied to that element. Finally, we’re passing in some rules to define our transition’s duration, its easing function, and then we’ve handed it done, which is a method that will be run when the velocity animation is complete. This lets Vue know this hook has completed, and it can move on to the next one in its lifecycle.

Note: We’ve used the easeOutCubic easing function, which is a one of many that are available to us thanks to Velocity. However, if you’re using this reference, know that the Back, Elastic and Bounce functions are not available to us via Velocity.

Leave Style

Now that we have defined how our drawer should enter, let’s define how it should leave().

📃 Drawer.vue

leave(el, done) {
Velocity(
el,
{ opacity: 0, width: '0em' },
{ duration: 500, easing: 'easeInCubic', complete: done }
)
}

Here, we’re again using the Velocity() method, passing it the el to animate, telling it to return to the styles we started with (invisible and no width), and we told it how to transition, setting the duration and

easing function, and then we passed it done so Vue knows when that hook is complete.

Note: It may seem odd to you that we’re using easeInCubic on our leave transition and easeOutCubic on our enter transition. But the “In” and “Out” only refer to the curve of the ease itself. The “In” and “Out” don’t correlate to how you should use them with an enter vs leave transitions. The best way to pick an easing function is to simply try them out and see which feels most natural for the transition you’re building.

Now, when we check this out in the browser, we’ll see that our user profile drawer slides nicely in and out of our interface.


The Power of Velocity

You might be thinking that all of this was overkill since we didn’t use Velocity for much that CSS alone couldn’t have accomplished (except for some special easing functions). But that’s because we started out quite simple. Velocity has more features we haven’t played with yet, such as spring physics which allows us to create springy/snappy/bouncy motion in our interfaces.

To see spring physics in action, let’s switch out the easing function on our enter() transition with a 2-item array.

📃 Drawer.vue

enter(el, done) {
Velocity(
el,
{ opacity: 1, width: '12em' },
{ duration: 1000, easing: [60, 10], complete: done } // now with spring physics
)
}

What are these numbers in the array? The first (60) is the amount of tension and the second (10) is the amount of friction for this spring function. We can tweak these to achieve dynamic effects. For example, a lower friction increases the ending vibration speed. Like easing functions, these numbers are something you’ll want to play around with until you find the right combination that feels natural for the effect you’re going for.

Now, when we check the browser, we’ll see our drawer slides open with a bit of spring in its step. Fun!


Another Example: Cards

To see another example of spring physics in action, in combination with the transition-group component, check out the Cards.vue component in this lesson’s ending code. You’ll see that the same concepts we just learned can be applied to another use case, where we might want cards to spring into view when a user loads the page. They could display anything from user reviews to pricing or product options… you name it.

Cards.vue

<template>
    <transition-group appear @before-enter="beforeEnter" @enter="enter" :css="false">
        <div class="card" v-for="card in cards" :key="card.id">
            <p>{{ card.title }}</p>
        </div>
    </transition-group>
</template>

<script>
    import Velocity from 'velocity-animate'

    export default {
        data() {
            return {
                cards: [{
                        title: 'Could contain anything',
                        id: 123
                    },
                    {
                        title: 'Endless possibilities',
                        id: 456
                    }
                ]
            }
        },
        methods: {
            beforeEnter(el) {
                el.style.opacity = 0
                el.style.width = '0em'
            },
            enter(el, done) {
                Velocity(
                    el, {
                        opacity: 1,
                        width: '12em',
                        rotateZ: '3deg'
                    }, {
                        duration: 1000,
                        easing: [70, 8],
                        complete: done
                    }
                )
            }
        }
    }
</script>

<style scoped>
    .card {
        height: 4em;
        width: 12em;
        border-radius: 1%;
        background-color: #e0e0e0;
        box-shadow: 0.08em 0.03em 0.4em #ababab;
        padding-top: 1em;
    }
</style>

Let’s ReVue

In this lesson, we learned how to build more complex motion into our interfaces by using JavaScript Hooks to trigger animation logic from a third-party library. Velocity is a lot more powerful than what we’ve touched on in this brief lesson, so I encourage you to continue playing with what is possible with the features it gives you.

In the next lesson, we’ll look at State Transitions along with an even more powerful animation library: GSAP.