Building Complex animations in Nuxt with GSAP

Animations add that wow factor to your application’s user experience. In this article, let’s explore creating some complex animations with GreenSock Animation Platform in Nuxt.

Charles Allotey
Nuxt 3

The Mastering Nuxt FullStack Unleashed Course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view course

Animations can elevate your static website into a dynamic, captivating experience that truly engages users. While Nuxt offers several built-in animation capabilities, implementing sophisticated animations often presents significant challenges when you're aiming to create more complex, interactive visual effects.

Incorporating GSAP (GreenSock Animation Platform) opens up powerful animation possibilities that are both performance-optimized and browser-compatible. Let's explore how to integrate and use GSAP for complex animations in your Nuxt projects.

What is GSAP?

GSAP is a robust JavaScript animation library that enables developers to animate virtually anything on the web. It's known for its flexibility, performance, and cross-browser compatibility. Unlike CSS animations, GSAP provides precise control over animation timelines, easing functions, and complex sequencing.

Setting Up GSAP in Nuxt

First, install GSAP in your Nuxt project:

npm install gsap

In Nuxt 3, you can create a plugin to make GSAP available throughout your application:

// plugins/gsap.js
import { defineNuxtPlugin } from '#app'
import { gsap } from 'gsap'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.provide('gsap', gsap)
})

Basic GSAP Animation in Nuxt

Let's start with a simple example. Let’s create a component that animates an element when it mounts:

<template>
  <div>
    <div ref="box" class="box"></div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useNuxtApp } from '#app'

const { $gsap } = useNuxtApp()
const box = ref(null)

onMounted(() => {
  $gsap.to(box.value, {
    x: 200,
    rotation: 360,
    backgroundColor: '#8d5cf6',
    duration: 2,
    ease: 'elastic.out(1, 0.3)'
  })
})
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: #42b883;
}
</style>

Let’s preview our animation

L82h7YUtGr8gfYshraWcEgBQJHvhz1v67uWHYZn1.gif

Building Complex Animation Timelines

GSAP's power lies in its ability to create complex animation sequences. Let's create a timeline that orchestrates multiple animations:

<template>
  <div class="container">
    <div ref="header" class="header">Welcome</div>
    <div ref="content" class="content">
      <div v-for="(item, index) in items" :key="index" :ref="el => itemRefs[index] = el" class="item">
        {{ item }}
      </div>
    </div>
    <button ref="cta" class="cta">Get Started</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useNuxtApp } from '#app'

const { $gsap } = useNuxtApp()
const header = ref(null)
const content = ref(null)
const cta = ref(null)
const items = ref(['Feature 1', 'Feature 2', 'Feature 3'])
const itemRefs = ref([])

onMounted(() => {
  const tl = $gsap.timeline({
    defaults: { duration: 0.8, ease: 'power3.out' }
  })

  tl.from(header.value, { y: -50, opacity: 0 })
    .from(itemRefs.value, {
      y: 30,
      opacity: 0,
      stagger: 0.2
    }, "-=0.5")
    .from(cta.value, {
      scale: 0.8,
      opacity: 0,
      ease: 'elastic.out(1, 0.3)'
    }, "-=0.3")
})
</script>

<style scoped>
.container {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
}
.header {
  font-size: 2rem;
  margin-bottom: 1rem;
}
.item {
  padding: 1rem;
  margin: 0.5rem 0;
  background-color: #f1f1f1;
  border-radius: 4px;
}
.cta {
  background-color: #42b883;
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 1rem;
}
</style>

Let’s see how it looks in our browser

YxFY3ARjA7VdXxjPRsVFcMj2uNUD8TLrtiyKq9SQ.gif

Scroll-Triggered Animations

One of the most effective uses of GSAP is creating scroll-triggered animations. To achieve this in Nuxt, we need to combine GSAP with its ScrollTrigger plugin:

// plugins/gsap.js
import { defineNuxtPlugin } from '#app'
import { gsap } from 'gsap'
import ScrollTrigger from 'gsap/ScrollTrigger'

gsap.registerPlugin(ScrollTrigger)

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.provide('gsap', gsap)
  nuxtApp.provide('ScrollTrigger', ScrollTrigger)
})

Now you can create scroll-triggered animations:

<template>
  <div>
    <div class="spacer"></div>
    <div ref="section" class="animated-section">
      <h2 ref="title">Scroll-Triggered Section</h2>
      <div ref="content" class="section-content">
        <p>This content will animate when scrolled into view.</p>
      </div>
    </div>
    <div class="spacer"></div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useNuxtApp } from '#app'

const { $gsap, $ScrollTrigger } = useNuxtApp()
const section = ref(null)
const title = ref(null)
const content = ref(null)

onMounted(() => {
  const tl = $gsap.timeline({
    scrollTrigger: {
      trigger: section.value,
      start: 'top 80%',
      end: 'bottom 20%',
      toggleActions: 'play none none reverse',
      markers: process.dev // Only show markers in development
    }
  })

  tl.from(title.value, {
    y: 50,
    opacity: 0,
    duration: 0.8
  })
  .from(content.value, {
    y: 30,
    opacity: 0,
    duration: 0.6
  }, "-=0.4")

  // Clean up ScrollTrigger instances when component unmounts
  return () => {
    $ScrollTrigger.getAll().forEach(trigger => trigger.kill())
  }
})
</script>

<style scoped>
.spacer {
  height: 100vh;
}
.animated-section {
  height: 80vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: #f5f5f5;
}
.section-content {
  max-width: 600px;
  padding: 2rem;
}
</style>

In our example we created a component which consists of a section sandwiched between two full-height spacers, with references to the main section, title, and content elements. When mounted, it creates an animation timeline that activates when the section enters the viewport (starting at 80% from top), causing the title to slide up from 50px below while fading in, followed by the content with a slight overlap.

Let’s preview our animation

fnnB9njTs9qxsr7zjrGWpue2DwCaEwAnr1jORCkG.gif

Advanced Techniques: Page Transitions

Nuxt's page transition system works beautifully with GSAP. Here's how to implement a custom page transition using GSAP:

// nuxt.config.js for Nuxt 3
export default defineNuxtConfig({
  app: {
    pageTransition: {
      name: 'page',
      mode: 'out-in'
    }
  }
})
<!-- assets/css/transitions.css -->
.page-enter-active,
.page-leave-active {
  position: absolute;
  width: 100%;
}

.page-enter-from,
.page-leave-to {
  opacity: 0;
}
<!-- app.vue -->
<template>
  <div>
    <Transition mode="out-in" @enter="onEnter" @leave="onLeave">
      <div :key="$route.fullPath">
        <NuxtPage />
      </div>
    </Transition>
  </div>
</template>

<script setup>
import { gsap } from 'gsap'

const onEnter = (el, done) => {
  console.log('enter triggered')
  gsap.fromTo(el, { opacity: 0, y: 100 }, {
    opacity: 1, y: 0, duration: 1, onComplete: done
  })
}

const onLeave = (el, done) => {
  console.log('leave triggered')
  gsap.to(el, { opacity: 0, y: -100, duration: 1, onComplete: done })
}
</script>

Now this combines both Nuxt’s page transitions and GSAP to create even more exciting animations when moving across pages. Let’s observe our implementation.

vlbFV26YJ6llDxC7pdjwDWK8ysoKEdEdxgYYuBno.gif

When implementing animations that demand some complexities, performance is also essential. Definitely make sure to explore GSAP’s performance recommendation to ensure your animation do not negatively affect the user’s experience

Conclusion

GSAP and Nuxt form a powerful combination for creating engaging web experiences. From simple element animations to complex page transitions and scroll effects, this pairing gives you the tools to elevate your web projects with professional-grade animations.

By understanding the basics of GSAP integration with Nuxt and exploring more complex techniques, you can create memorable user experiences that set your applications apart. The key is to balance impressive visual effects with performance considerations to ensure smooth experiences across all devices.

Useful Resources:

GSAP

Nuxt Page Transitions

Github Repository

Charles Allotey
Charles is a Frontend Developer at Vueschool. Has a passion for building great experiences and products using Vue.js and Nuxt.

Follow MasteringNuxt on