Skip to content

GSAP Animations

This guide explains GSAP animation patterns and best practices in LamaPress.

Table of Contents


Overview

LamaPress uses GSAP (GreenSock Animation Platform) for all animations. GSAP provides smooth, performant animations with excellent browser support and powerful features like ScrollTrigger.

GSAP Setup

Importing GSAP

Import GSAP in component files:

javascript
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/all'

Note: GSAP is configured globally in src/js/core/utils/gsapConfig.js.

Plugin Registration

GSAP plugins are registered automatically:

javascript
// ScrollTrigger is registered globally
gsap.registerPlugin(ScrollTrigger)

Animation Patterns

Reveal Animations

Reveal animations in enter() method:

javascript
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/all'

export default class AnimatedSection {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    this.title = this.element.querySelector('.js-title')
    this.content = this.element.querySelector('.js-content')
    
    // Set initial state
    gsap.set([this.title, this.content], {
      opacity: 0,
      y: 20,
    })
  }

  enter = () => {
    this.timeline = gsap.timeline({
      scrollTrigger: {
        trigger: this.element,
        start: 'top 80%',
      },
    })

    this.timeline.to(this.title, {
      opacity: 1,
      y: 0,
      duration: 0.6,
    })

    this.timeline.to(this.content, {
      opacity: 1,
      y: 0,
      duration: 0.6,
    }, '-=0.3') // Start 0.3s before previous animation ends
  }

  destroy = () => {
    this.timeline?.kill()
    ScrollTrigger.getAll().forEach(trigger => {
      if (trigger.vars.trigger === this.element) {
        trigger.kill()
      }
    })
  }
}

Hover Animations

Hover animations with mouseenter/mouseleave:

javascript
import gsap from 'gsap'
import { killTimeline } from '@src/js/core/utils/helpers'

export default class HoverCard {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    this.image = this.element.querySelector('.js-image')
    this.bindEvents()
  }

  bindEvents = () => {
    this.element.addEventListener('mouseenter', this.onMouseEnter)
    this.element.addEventListener('mouseleave', this.onMouseLeave)
  }

  onMouseEnter = () => {
    killTimeline(this.hoverTimeline)
    
    this.hoverTimeline = gsap.timeline()
    this.hoverTimeline.to(this.image, {
      scale: 1.1,
      duration: 0.3,
      ease: 'power2.out',
    })
  }

  onMouseLeave = () => {
    killTimeline(this.hoverTimeline)
    
    this.hoverTimeline = gsap.timeline()
    this.hoverTimeline.to(this.image, {
      scale: 1,
      duration: 0.3,
      ease: 'power2.out',
    })
  }

  destroy = () => {
    killTimeline(this.hoverTimeline)
    this.element.removeEventListener('mouseenter', this.onMouseEnter)
    this.element.removeEventListener('mouseleave', this.onMouseLeave)
  }
}

Scroll Animations

Scroll-based animations with ScrollTrigger:

javascript
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/all'

export default class ScrollAnimation {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    this.content = this.element.querySelector('.js-content')
  }

  enter = () => {
    gsap.fromTo(
      this.content,
      {
        opacity: 0,
        y: 50,
      },
      {
        opacity: 1,
        y: 0,
        duration: 1,
        ease: 'power3.out',
        scrollTrigger: {
          trigger: this.element,
          start: 'top 80%',
          end: 'top 20%',
          scrub: true, // Smooth scroll-linked animation
        },
      }
    )
  }

  destroy = () => {
    ScrollTrigger.getAll().forEach(trigger => {
      if (trigger.vars.trigger === this.element) {
        trigger.kill()
      }
    })
  }
}

Timeline Management

Creating Timelines

Create timelines in init() or enter():

javascript
this.timeline = gsap.timeline({
  paused: true, // Start paused
  onComplete: () => {
    // Callback when complete
  },
})

Killing Timelines

Always kill timelines in destroy():

javascript
destroy = () => {
  this.timeline?.kill()
}

Or use helper:

javascript
import { killTimeline } from '@src/js/core/utils/helpers'

destroy = () => {
  killTimeline(this.timeline)
}

ScrollTrigger

Basic ScrollTrigger

Simple scroll-triggered animation:

javascript
gsap.to(element, {
  opacity: 1,
  scrollTrigger: {
    trigger: element,
    start: 'top 80%',
  },
})

Advanced ScrollTrigger

Advanced scroll animations:

javascript
gsap.to(element, {
  y: -100,
  scrollTrigger: {
    trigger: element,
    start: 'top top',
    end: 'bottom top',
    scrub: 1, // Smooth scroll-linked
    pin: true, // Pin element during scroll
  },
})

Best Practices

1. Bind Timelines to Instance

Always bind timelines to component instance:

javascript
this.timeline = gsap.timeline()
// Not: const timeline = gsap.timeline()

2. Clean Up in destroy()

Always clean up animations:

javascript
destroy = () => {
  this.timeline?.kill()
  ScrollTrigger.getAll().forEach(trigger => {
    if (trigger.vars.trigger === this.element) {
      trigger.kill()
    }
  })
}

3. Use killTimeline() Helper

Use helper function for hover animations:

javascript
import { killTimeline } from '@src/js/core/utils/helpers'

onMouseEnter = () => {
  killTimeline(this.hoverTimeline)
  // Create new timeline
}

Next Steps:

Released under the MIT License.