Skip to main content

Vue.js Custom Animations

Introduction

Animation adds life and interactivity to web applications, improving user experience and making interfaces more intuitive. While Vue.js provides built-in transition components, creating custom animations allows for unique, tailored experiences that can set your application apart.

In this guide, we'll explore how to create custom animations in Vue.js that go beyond the basics. We'll leverage Vue's transition system alongside CSS and JavaScript to craft smooth, engaging animations that enhance your application's interactivity.

Understanding Animation Fundamentals in Vue

Before diving into custom animations, it's important to understand the foundation of Vue's animation system:

  1. Vue's <Transition> component provides hooks for entering and leaving transitions
  2. Animations can be implemented using:
    • CSS transitions
    • CSS animations
    • JavaScript hooks for more complex animations

Creating Custom CSS Transitions

Basic Custom Transition

Let's start with a custom fade-slide transition:

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

<Transition name="fade-slide">
<p v-if="show" class="demo-element">Hello Animation World!</p>
</Transition>
</div>
</template>

<script>
export default {
data() {
return {
show: true
}
}
}
</script>

<style>
.demo-element {
padding: 15px;
background: #42b883;
color: white;
border-radius: 4px;
}

/* Custom transition classes */
.fade-slide-enter-active,
.fade-slide-leave-active {
transition: all 0.5s ease;
}

.fade-slide-enter-from,
.fade-slide-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>

In this example, we've created a custom transition that combines fading and sliding. Elements will fade in while sliding from the right when entering, and fade out while sliding to the right when leaving.

Custom Bounce Animation

Let's create a more dynamic bounce animation:

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

<Transition name="bounce">
<div v-if="show" class="bounce-box">
<p>Bouncy Content!</p>
</div>
</Transition>
</div>
</template>

<script>
export default {
data() {
return {
show: true
}
}
}
</script>

<style>
.bounce-box {
padding: 20px;
background: #ff7e47;
color: white;
border-radius: 8px;
margin-top: 20px;
}

.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-out 0.5s;
}

@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}

@keyframes bounce-out {
0% {
transform: scale(1);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(0);
}
}
</style>

This creates a playful bounce effect using CSS keyframe animations rather than simple transitions.

Advanced Custom Animations with JavaScript Hooks

For more complex animations, Vue.js provides JavaScript hooks within the transition system.

Interactive Animation with JavaScript Hooks

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

<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
>
<div v-if="show" class="card">
<h3>Custom JS Animation</h3>
<p>This card is animated with JavaScript hooks!</p>
</div>
</Transition>
</div>
</template>

<script>
import gsap from 'gsap' // Make sure to npm install gsap

export default {
data() {
return {
show: true
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0
el.style.transformOrigin = 'left center'
},
enter(el, done) {
gsap.to(el, {
duration: 0.8,
opacity: 1,
x: 0,
rotationY: 0,
ease: 'elastic.out(1, 0.5)',
onComplete: done
})
},
afterEnter(el) {
console.log('Animation complete')
},
beforeLeave(el) {
el.style.transformOrigin = 'left center'
},
leave(el, done) {
gsap.to(el, {
duration: 0.6,
opacity: 0,
rotationY: 90,
x: -100,
ease: 'back.in(1.7)',
onComplete: done
})
},
afterLeave(el) {
console.log('Element removed')
}
}
}
</script>

<style>
.card {
padding: 20px;
background: #4834d4;
color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
width: 300px;
margin-top: 20px;
}
</style>

In this example, we use the GSAP animation library to create a 3D card flip effect with elastic easing. The JavaScript hooks give us fine-grained control over the animation's timing and effects.

Creating Reusable Animation Components

For consistent animations across your application, consider creating reusable animation components:

html
<template>
<Transition
:name="name"
:mode="mode"
:appear="appear"
v-bind="$attrs"
>
<slot></slot>
</Transition>
</template>

<script>
export default {
name: 'FadeTransition',
props: {
name: {
type: String,
default: 'fade'
},
mode: {
type: String,
default: 'in-out'
},
appear: {
type: Boolean,
default: false
}
}
}
</script>

<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}

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

Then you can use it throughout your application:

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

<FadeTransition appear>
<p v-if="show" class="content">This content uses a reusable fade transition!</p>
</FadeTransition>
</div>
</template>

<script>
import FadeTransition from './components/FadeTransition.vue'

export default {
components: {
FadeTransition
},
data() {
return {
show: true
}
}
}
</script>

Real-World Application: Animated Shopping Cart

Let's implement a practical example - an animated shopping cart:

html
<template>
<div class="shop-container">
<div class="products">
<div v-for="(product, index) in products" :key="index" class="product">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
<button @click="addToCart(product)">Add to Cart</button>
</div>
</div>

<div class="cart">
<h2>Shopping Cart</h2>
<TransitionGroup name="list" tag="ul">
<li v-for="(item, index) in cart" :key="item.id" class="cart-item">
<span>{{ item.name }} - ${{ item.price }}</span>
<button @click="removeFromCart(index)" class="remove-btn">×</button>
</li>
</TransitionGroup>

<Transition name="fade">
<div v-if="cart.length > 0" class="cart-total">
Total: ${{ cartTotal }}
</div>
</Transition>

<Transition name="bounce">
<div v-if="cart.length === 0" class="empty-cart">
Your cart is empty
</div>
</Transition>
</div>
</div>
</template>

<script>
export default {
data() {
return {
products: [
{ id: 1, name: 'Vue T-Shirt', price: 25 },
{ id: 2, name: 'Vue Hoodie', price: 45 },
{ id: 3, name: 'Vue Stickers', price: 8 }
],
cart: []
}
},
computed: {
cartTotal() {
return this.cart.reduce((sum, item) => sum + item.price, 0)
}
},
methods: {
addToCart(product) {
// Create a new object to avoid reference issues
const productWithUniqueId = {
...product,
id: Date.now() // Unique ID for animation purposes
}
this.cart.push(productWithUniqueId)
},
removeFromCart(index) {
this.cart.splice(index, 1)
}
}
}
</script>

<style>
.shop-container {
display: flex;
gap: 30px;
}

.products {
flex: 1;
}

.product {
padding: 15px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}

.cart {
flex: 1;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
}

.cart-item {
display: flex;
justify-content: space-between;
padding: 10px;
background: #f9f9f9;
margin-bottom: 8px;
border-radius: 4px;
}

.remove-btn {
background: #ff4757;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
}

.cart-total {
margin-top: 20px;
font-weight: bold;
font-size: 1.2em;
}

.empty-cart {
padding: 20px;
text-align: center;
color: #999;
}

/* Animation styles */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}

.list-enter-from {
opacity: 0;
transform: translateX(30px);
}

.list-leave-to {
opacity: 0;
transform: translateX(-30px);
}

/* Make sure moved items animate smoothly */
.list-move {
transition: transform 0.5s;
}

.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-out 0.5s;
}

@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}

@keyframes bounce-out {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(0);
}
}
</style>

In this shopping cart example, we've implemented several animations:

  1. Items slide in from the right when added to the cart
  2. Items slide out to the left when removed
  3. The total price fades in and out
  4. When the cart is empty, a message bounces in

Page Transitions for Single Page Applications

For SPA route transitions, we can create smooth page transitions:

html
<template>
<div class="app">
<nav class="nav-bar">
<router-link to="/">Home</router-link>
<router-link to="/products">Products</router-link>
<router-link to="/about">About</router-link>
</nav>

<Transition name="page" mode="out-in">
<router-view></router-view>
</Transition>
</div>
</template>

<style>
.nav-bar {
display: flex;
gap: 20px;
margin-bottom: 20px;
}

.page-enter-active,
.page-leave-active {
transition: opacity 0.3s, transform 0.3s;
}

.page-enter-from {
opacity: 0;
transform: translateY(30px);
}

.page-leave-to {
opacity: 0;
transform: translateY(-30px);
}
</style>

Staggered List Animations

Creating staggered animations for lists can add a polished feel:

html
<template>
<div>
<button @click="show = !show">Toggle List</button>
<TransitionGroup
tag="ul"
:css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<li v-for="(item, index) in show ? items : []"
:key="item"
:data-index="index"
class="staggered-item">
{{ item }}
</li>
</TransitionGroup>
</div>
</template>

<script>
import gsap from 'gsap'

export default {
data() {
return {
show: true,
items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0
el.style.transform = 'translateY(30px)'
},
enter(el, done) {
const delay = el.dataset.index * 0.15
gsap.to(el, {
opacity: 1,
y: 0,
delay,
duration: 0.3,
onComplete: done
})
},
leave(el, done) {
const delay = el.dataset.index * 0.15
gsap.to(el, {
opacity: 0,
y: -30,
delay,
duration: 0.3,
onComplete: done
})
}
}
}
</script>

<style>
.staggered-item {
margin: 10px;
padding: 10px 20px;
background: #3498db;
color: white;
border-radius: 4px;
display: inline-block;
}
</style>

This creates a staggered animation where list items animate in and out with a small delay between each item, creating a cascade effect.

Animation Performance Tips

When creating custom animations, keep these performance tips in mind:

  1. Animate only transform and opacity properties when possible (these don't trigger layout recalculations)
  2. Use will-change for elements that will animate frequently
  3. Keep animations short (less than 300ms) for UI responses
  4. Use requestAnimationFrame for manual animations instead of setTimeout
  5. Test on mobile devices to ensure smooth performance

Example of using will-change:

html
<style>
.animated-element {
will-change: transform, opacity;
transition: transform 0.3s, opacity 0.3s;
}
</style>

Summary

In this guide, we explored how to create custom animations in Vue.js applications:

  1. We began with basic CSS transitions and animations using Vue's transition component
  2. We discovered how to implement more complex animations with JavaScript hooks
  3. We created reusable animation components for better code organization
  4. We implemented practical real-world examples like an animated shopping cart
  5. We explored page transitions, staggered animations, and performance tips

By combining Vue's transition system with CSS and JavaScript animations, you can create engaging, interactive experiences that make your applications stand out. Remember that animations should enhance user experience rather than distract from it. Use animations purposefully to guide users, provide feedback, and make interfaces more intuitive.

Additional Resources and Exercises

Resources

Exercises

  1. Animation Playground: Create a component that demonstrates different transition types with controls to adjust duration, delay, and easing.

  2. Animated Form: Build a form with animated validation feedback (shake animation for errors, checkmark animation for valid fields).

  3. Interactive Dashboard: Create a dashboard with elements that animate in on page load in a staggered sequence.

  4. Animated Modal: Build a custom modal component with an engaging entrance and exit animation.

  5. Route Transitions: Implement custom page transitions for different route types in a Vue Router application.

Master these custom animation techniques, and you'll be able to create more engaging, intuitive, and polished Vue.js applications.



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