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:
- Vue's
<Transition>
component provides hooks for entering and leaving transitions - 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:
<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:
<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
<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:
<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:
<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:
<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:
- Items slide in from the right when added to the cart
- Items slide out to the left when removed
- The total price fades in and out
- 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:
<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:
<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:
- Animate only transform and opacity properties when possible (these don't trigger layout recalculations)
- Use
will-change
for elements that will animate frequently - Keep animations short (less than 300ms) for UI responses
- Use
requestAnimationFrame
for manual animations instead ofsetTimeout
- Test on mobile devices to ensure smooth performance
Example of using will-change
:
<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:
- We began with basic CSS transitions and animations using Vue's transition component
- We discovered how to implement more complex animations with JavaScript hooks
- We created reusable animation components for better code organization
- We implemented practical real-world examples like an animated shopping cart
- 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
- Vue.js Transitions & Animation Official Guide
- GSAP Animation Library
- CSS Tricks: A Guide to Vue.js Transitions
Exercises
-
Animation Playground: Create a component that demonstrates different transition types with controls to adjust duration, delay, and easing.
-
Animated Form: Build a form with animated validation feedback (shake animation for errors, checkmark animation for valid fields).
-
Interactive Dashboard: Create a dashboard with elements that animate in on page load in a staggered sequence.
-
Animated Modal: Build a custom modal component with an engaging entrance and exit animation.
-
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! :)