Skip to main content

Vue.js JavaScript Animations

Introduction

While Vue's built-in transition system is powerful for many use cases, sometimes you need more control over your animations. That's where JavaScript animations come in. JavaScript animations in Vue.js allow you to create complex, dynamic, and interactive animations that may be difficult to achieve with CSS alone.

In this tutorial, you'll learn how to leverage Vue's JavaScript hooks to create custom animations, explore animation libraries integration, and build real-world animation examples that will make your applications more engaging and interactive.

Understanding Vue's JavaScript Animation Hooks

Vue's transition component provides JavaScript hooks that are triggered at different stages of a transition. These hooks give you full programmatic control over the animation process.

Available JavaScript Hooks

js
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<!-- content -->
</Transition>

Creating a Basic JavaScript Animation

Let's start with a simple example of a JavaScript animation using Vue's hooks. We'll animate an element's opacity and position.

html
<template>
<div>
<button @click="show = !show">Toggle</button>

<Transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<div v-if="show" class="js-animated-box"></div>
</Transition>
</div>
</template>

<script>
import { ref } from 'vue';
import gsap from 'gsap'; // We'll use GSAP library for this example

export default {
setup() {
const show = ref(true);

const beforeEnter = (el) => {
el.style.opacity = 0;
el.style.transform = 'translateX(-100px)';
};

const enter = (el, done) => {
gsap.to(el, {
opacity: 1,
translateX: 0,
duration: 1,
onComplete: done
});
};

const leave = (el, done) => {
gsap.to(el, {
opacity: 0,
translateX: 100,
duration: 0.8,
onComplete: done
});
};

return {
show,
beforeEnter,
enter,
leave
};
}
};
</script>

<style>
.js-animated-box {
width: 100px;
height: 100px;
background-color: #3eaf7c;
margin: 20px;
}
</style>

In this example:

  1. beforeEnter sets the initial state of the element
  2. enter animates the element to its final state when it appears
  3. leave animates the element before it's removed from the DOM

The important thing to note is that for enter and leave hooks, you need to call the done callback when the animation completes. This signals to Vue that the transition is finished.

Combining CSS and JavaScript Animations

You can combine CSS transitions with JavaScript hooks for more complex animations:

html
<template>
<div>
<button @click="show = !show">Toggle</button>

<Transition
name="bounce"
@before-enter="beforeEnter"
@enter="enter"
>
<div v-if="show" class="combined-box"></div>
</Transition>
</div>
</template>

<script>
import { ref } from 'vue';

export default {
setup() {
const show = ref(true);

const beforeEnter = (el) => {
el.style.transform = 'scale(0)';
};

const enter = (el, done) => {
// Create a small delay
setTimeout(() => {
// This triggers the CSS transition
el.style.transform = 'scale(1)';

// Wait for transition to finish before calling done
el.addEventListener('transitionend', done, { once: true });
}, 20);
};

return {
show,
beforeEnter,
enter
};
}
};
</script>

<style>
.combined-box {
width: 100px;
height: 100px;
background-color: #42b983;
margin: 20px;
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}

.bounce-enter-active {
transition: all 0.5s ease;
}

.bounce-leave-active {
transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1);
}

.bounce-enter-from,
.bounce-leave-to {
opacity: 0;
}
</style>

This example combines CSS transitions with JavaScript initialization to create a bouncy scale effect.

Integrating Animation Libraries

For more complex animations, integrating a dedicated animation library is often the best approach. Let's look at how to integrate three popular libraries:

Using GreenSock Animation Platform (GSAP)

GSAP is a professional-grade animation library that makes complex animations more manageable.

html
<template>
<div>
<button @click="show = !show">Toggle</button>
<button @click="playAnimation">Animate</button>

<Transition
@enter="onEnter"
@leave="onLeave"
>
<div v-if="show" class="gsap-box" ref="animatedBox"></div>
</Transition>
</div>
</template>

<script>
import { ref } from 'vue';
import gsap from 'gsap';

export default {
setup() {
const show = ref(true);
const animatedBox = ref(null);

const onEnter = (el, done) => {
gsap.from(el, {
y: -50,
opacity: 0,
scale: 0.5,
duration: 0.8,
ease: "bounce.out",
onComplete: done
});
};

const onLeave = (el, done) => {
gsap.to(el, {
y: 100,
opacity: 0,
duration: 0.6,
onComplete: done
});
};

const playAnimation = () => {
if (!animatedBox.value) return;

gsap.to(animatedBox.value, {
rotation: "+=360",
scale: 1.2,
duration: 1,
ease: "power1.inOut",
yoyo: true,
repeat: 1
});
};

return {
show,
animatedBox,
onEnter,
onLeave,
playAnimation
};
}
};
</script>

<style>
.gsap-box {
width: 100px;
height: 100px;
background-color: #ff7e67;
margin: 20px;
}
</style>

Using Anime.js

Anime.js is another lightweight animation library with a simple API.

html
<template>
<div>
<button @click="show = !show">Toggle</button>
<button @click="animatePath">Animate SVG</button>

<Transition
@enter="onEnter"
@leave="onLeave"
>
<div v-if="show" class="anime-box"></div>
</Transition>

<svg width="200" height="200">
<path
ref="path"
d="M10,80 C50,10 150,10 190,80 S130,150 10,80"
fill="none"
stroke="#42b983"
stroke-width="5"
/>
</svg>
</div>
</template>

<script>
import { ref } from 'vue';
import anime from 'animejs';

export default {
setup() {
const show = ref(true);
const path = ref(null);

const onEnter = (el, done) => {
anime({
targets: el,
translateY: [-50, 0],
opacity: [0, 1],
scale: [0.8, 1],
duration: 800,
easing: 'easeOutElastic(1, .5)',
complete: done
});
};

const onLeave = (el, done) => {
anime({
targets: el,
translateY: 50,
opacity: 0,
duration: 600,
easing: 'easeInQuad',
complete: done
});
};

const animatePath = () => {
anime({
targets: path.value,
d: [
'M10,80 C50,10 150,10 190,80 S130,150 10,80',
'M10,80 C80,30 120,30 190,80 S130,120 10,80'
],
duration: 1000,
easing: 'easeInOutQuad',
direction: 'alternate',
loop: true
});
};

return {
show,
path,
onEnter,
onLeave,
animatePath
};
}
};
</script>

<style>
.anime-box {
width: 100px;
height: 100px;
background-color: #42b983;
margin: 20px;
}
</style>

Real-World Example: Animated Page Transitions

Let's create a more practical example: animated page transitions in a Vue Router application.

html
<template>
<div class="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/contact">Contact</router-link>
</nav>

<Transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</Transition>
</div>
</template>

<script>
import { useRoute } from 'vue-router';
import { ref, watch } from 'vue';
import gsap from 'gsap';

export default {
setup() {
const route = useRoute();
const direction = ref('right');

// Determine animation direction based on route changes
watch(
() => route.path,
(newPath, oldPath) => {
const routeOrder = ['/', '/about', '/contact'];
const newIndex = routeOrder.indexOf(newPath);
const oldIndex = routeOrder.indexOf(oldPath);

direction.value = newIndex > oldIndex ? 'right' : 'left';
}
);

const beforeEnter = (el) => {
el.style.opacity = 0;
el.style.transform = `translateX(${direction.value === 'right' ? '100px' : '-100px'})`;
};

const enter = (el, done) => {
gsap.to(el, {
opacity: 1,
translateX: 0,
duration: 0.5,
ease: 'power2.out',
onComplete: done
});
};

const leave = (el, done) => {
gsap.to(el, {
opacity: 0,
translateX: direction.value === 'right' ? '-100px' : '100px',
duration: 0.5,
ease: 'power2.in',
onComplete: done
});
};

return {
beforeEnter,
enter,
leave
};
}
};
</script>

<style>
.app {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}

nav {
margin-bottom: 30px;
}
</style>

This example determines the animation direction based on the navigation flow between routes. If you move from Home to About, the content slides in from the right. If you go back, it slides in from the left.

Advanced Technique: Staggered Lists

A common UI pattern is to animate items in a list with a staggered delay. Here's how to accomplish this with Vue and GSAP:

html
<template>
<div>
<button @click="addItem">Add Item</button>
<button @click="removeItem">Remove Item</button>
<button @click="shuffleItems">Shuffle</button>

<TransitionGroup
tag="ul"
class="staggered-list"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<li v-for="(item, index) in items" :key="item.id" :data-index="index">
{{ item.text }}
</li>
</TransitionGroup>
</div>
</template>

<script>
import { ref } from 'vue';
import gsap from 'gsap';

export default {
setup() {
const items = ref([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
let nextId = 4;

const beforeEnter = (el) => {
el.style.opacity = 0;
el.style.height = 0;
};

const enter = (el, done) => {
const delay = el.dataset.index * 0.15;

gsap.to(el, {
opacity: 1,
height: '3em',
delay,
duration: 0.4,
onComplete: done
});
};

const leave = (el, done) => {
const delay = el.dataset.index * 0.15;

gsap.to(el, {
opacity: 0,
height: 0,
delay,
duration: 0.4,
onComplete: done
});
};

const addItem = () => {
const pos = Math.floor(Math.random() * (items.value.length + 1));
items.value.splice(pos, 0, {
id: nextId++,
text: `Item ${nextId - 1}`
});
};

const removeItem = () => {
if (items.value.length > 0) {
const pos = Math.floor(Math.random() * items.value.length);
items.value.splice(pos, 1);
}
};

const shuffleItems = () => {
items.value = items.value
.slice()
.sort(() => Math.random() - 0.5);
};

return {
items,
beforeEnter,
enter,
leave,
addItem,
removeItem,
shuffleItems
};
}
};
</script>

<style>
.staggered-list {
list-style-type: none;
padding: 0;
}

.staggered-list li {
padding: 10px 20px;
margin: 5px 0;
background-color: #f5f5f5;
border-radius: 4px;
overflow: hidden;
}
</style>

This example creates a list where items animate in and out with a staggered delay based on their position in the list.

Performance Considerations

When working with JavaScript animations, keep these performance tips in mind:

  1. Avoid animating expensive CSS properties: Properties like width, height, and top can cause layout recalculations. Instead, prefer animating transform and opacity.

  2. Use will-change wisely: For complex animations, you can hint to the browser:

css
.animated-element {
will-change: transform, opacity;
}
  1. Debounce window resize handlers: If your animations react to window size, debounce the resize event to prevent excessive calculations.

  2. Clean up animations: Make sure to clean up any ongoing animations when components are unmounted:

js
onBeforeUnmount(() => {
// If using GSAP
gsap.killTweensOf(elementRef.value);

// Or if using timers
clearTimeout(timerRef.value);
});

Summary

JavaScript animations in Vue.js provide powerful capabilities for creating dynamic, engaging user interfaces. While CSS transitions are sufficient for simple effects, JavaScript animations give you more control and flexibility for complex interactions.

In this tutorial, you've learned:

  • How to use Vue's transition JavaScript hooks
  • How to integrate popular animation libraries like GSAP and Anime.js
  • How to create real-world animations for page transitions and staggered lists
  • Best practices for animation performance

With these techniques, you can create professional-grade animations that enhance the user experience of your Vue applications.

Additional Resources

To deepen your knowledge of Vue.js animations with JavaScript:

Exercises

  1. Create a card flip animation using JavaScript hooks that shows different content on each side.
  2. Implement a loading animation that uses SVG paths and JavaScript animation.
  3. Create a notification system where messages slide in, stay for a few seconds, and slide out.
  4. Build a photo gallery with zoom animations when clicking on images.
  5. Implement a staggered animation for a navigation menu that reveals submenu items with a cascade effect.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)