Agent Skills: GSAP ScrollTrigger

Scroll-based animations using GSAP ScrollTrigger plugin including pinning, scrubbing, snap points, and parallax effects. Use when creating scroll-driven animations, sticky sections, progress indicators, or parallax scrolling experiences.

UncategorizedID: Bbeierle12/Skill-MCP-Claude/gsap-scrolltrigger

Skill Files

Browse the full folder contents for gsap-scrolltrigger.

Download Skill

Loading file tree…

skills/gsap-scrolltrigger/SKILL.md

Skill Metadata

Name
gsap-scrolltrigger
Description
Scroll-based animations using GSAP ScrollTrigger plugin including pinning, scrubbing, snap points, and parallax effects. Use when creating scroll-driven animations, sticky sections, progress indicators, or parallax scrolling experiences.

GSAP ScrollTrigger

Scroll-driven animations and interactions.

Quick Start

npm install gsap
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

gsap.to('.box', {
  x: 500,
  scrollTrigger: {
    trigger: '.box',
    start: 'top center',
    end: 'bottom center',
    scrub: true
  }
});

Core Concepts

Basic ScrollTrigger

gsap.to('.element', {
  x: 200,
  scrollTrigger: {
    trigger: '.element',  // Element that triggers the animation
    start: 'top center',  // When trigger hits viewport center
    end: 'bottom center', // When trigger leaves viewport center
    toggleActions: 'play pause resume reset'
  }
});

Start/End Positions

// Format: "trigger-position viewport-position"
start: 'top center'      // Trigger's top hits viewport center
start: 'top 80%'         // Trigger's top hits 80% down viewport
start: 'center center'   // Trigger's center hits viewport center
start: 'bottom top'      // Trigger's bottom hits viewport top
start: 'top top+=100'    // Trigger's top hits 100px below viewport top

Position Reference

| Value | Description | |-------|-------------| | top | Top edge | | center | Center | | bottom | Bottom edge | | 80% | 80% from top | | +=100 | Plus 100 pixels | | -=50 | Minus 50 pixels |

Scrub Animations

Basic Scrub

// Animation progress tied to scroll position
gsap.to('.progress-bar', {
  scaleX: 1,
  scrollTrigger: {
    trigger: '.content',
    start: 'top top',
    end: 'bottom bottom',
    scrub: true  // Directly linked to scroll
  }
});

Smooth Scrub

gsap.to('.element', {
  x: 500,
  scrollTrigger: {
    trigger: '.section',
    scrub: 1,    // 1 second smoothing
    // scrub: 0.5  // 0.5 second smoothing
    // scrub: 2    // 2 second smoothing (laggy feel)
  }
});

Scrub with Timeline

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.container',
    start: 'top top',
    end: '+=3000',  // Scroll distance
    scrub: 1,
    pin: true
  }
});

tl.to('.step1', { opacity: 1 })
  .to('.step2', { opacity: 1 })
  .to('.step3', { opacity: 1 });

Pinning

Basic Pin

ScrollTrigger.create({
  trigger: '.panel',
  start: 'top top',
  end: '+=500',      // Pin for 500px of scroll
  pin: true
});

Pin with Animation

gsap.to('.content', {
  x: '-200%',
  ease: 'none',
  scrollTrigger: {
    trigger: '.horizontal-section',
    start: 'top top',
    end: () => '+=' + document.querySelector('.horizontal-section').offsetWidth,
    pin: true,
    scrub: 1
  }
});

Pin Spacing

ScrollTrigger.create({
  trigger: '.section',
  pin: true,
  pinSpacing: true,   // Default: adds space for pinned duration
  // pinSpacing: false  // No extra space (content overlaps)
  // pinSpacing: '500px' // Custom spacing
});

Toggle Actions

Action Syntax

// Format: "onEnter onLeave onEnterBack onLeaveBack"
toggleActions: 'play pause resume reset'

// Common combinations:
toggleActions: 'play none none none'     // Play once
toggleActions: 'play reverse play reverse' // Toggle direction
toggleActions: 'restart none none none'  // Restart each time
toggleActions: 'play complete reverse reset'

Action Values

| Action | Effect | |--------|--------| | play | Play forward | | pause | Pause | | resume | Resume from paused | | reverse | Play backward | | restart | Restart from beginning | | reset | Reset to start (no animation) | | complete | Jump to end | | none | Do nothing |

Snap Points

Basic Snap

ScrollTrigger.create({
  trigger: '.sections',
  start: 'top top',
  end: 'bottom bottom',
  snap: 1 / 4  // Snap to quarters
});

Snap to Labels

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.container',
    scrub: 1,
    snap: {
      snapTo: 'labels',
      duration: 0.5,
      ease: 'power2.inOut'
    }
  }
});

tl.addLabel('intro')
  .to('.a', { opacity: 1 })
  .addLabel('middle')
  .to('.b', { opacity: 1 })
  .addLabel('end');

Snap Configuration

snap: {
  snapTo: [0, 0.25, 0.5, 0.75, 1],  // Snap to specific points
  duration: { min: 0.2, max: 0.6 }, // Snap duration range
  delay: 0,                          // Delay before snap
  ease: 'power1.inOut',             // Snap easing
  directional: true                  // Snap in scroll direction
}

Callbacks

ScrollTrigger Callbacks

ScrollTrigger.create({
  trigger: '.section',
  onEnter: () => console.log('Entered'),
  onLeave: () => console.log('Left'),
  onEnterBack: () => console.log('Entered from bottom'),
  onLeaveBack: () => console.log('Left going up'),
  onUpdate: (self) => console.log('Progress:', self.progress),
  onToggle: (self) => console.log('Active:', self.isActive),
  onRefresh: () => console.log('Refreshed')
});

Progress-Based Updates

ScrollTrigger.create({
  trigger: '.section',
  start: 'top bottom',
  end: 'bottom top',
  onUpdate: (self) => {
    // self.progress: 0 to 1
    // self.direction: 1 (down) or -1 (up)
    // self.velocity: scroll speed
    updateElement(self.progress);
  }
});

Parallax Effects

Basic Parallax

// Background moves slower than scroll
gsap.to('.background', {
  yPercent: -50,
  ease: 'none',
  scrollTrigger: {
    trigger: '.section',
    scrub: true
  }
});

// Foreground moves faster
gsap.to('.foreground', {
  yPercent: 50,
  ease: 'none',
  scrollTrigger: {
    trigger: '.section',
    scrub: true
  }
});

Multi-Layer Parallax

const layers = [
  { selector: '.layer-1', speed: -20 },
  { selector: '.layer-2', speed: -40 },
  { selector: '.layer-3', speed: -60 },
  { selector: '.layer-4', speed: -80 }
];

layers.forEach(layer => {
  gsap.to(layer.selector, {
    yPercent: layer.speed,
    ease: 'none',
    scrollTrigger: {
      trigger: '.parallax-section',
      start: 'top bottom',
      end: 'bottom top',
      scrub: true
    }
  });
});

Horizontal Scrolling

Horizontal Section

const sections = gsap.utils.toArray('.panel');

gsap.to(sections, {
  xPercent: -100 * (sections.length - 1),
  ease: 'none',
  scrollTrigger: {
    trigger: '.horizontal-container',
    pin: true,
    scrub: 1,
    snap: 1 / (sections.length - 1),
    end: () => '+=' + document.querySelector('.horizontal-container').offsetWidth
  }
});

Markers (Development)

ScrollTrigger.create({
  trigger: '.section',
  start: 'top center',
  end: 'bottom center',
  markers: true,  // Show visual markers
  // markers: { startColor: 'green', endColor: 'red', fontSize: '12px' }
});

Batch Animations

Stagger on Scroll

ScrollTrigger.batch('.card', {
  onEnter: (elements) => {
    gsap.from(elements, {
      opacity: 0,
      y: 50,
      stagger: 0.1,
      duration: 0.5
    });
  },
  start: 'top 85%'
});

Batch Configuration

ScrollTrigger.batch('.item', {
  interval: 0.1,  // Time between batches
  batchMax: 3,    // Max items per batch
  onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, stagger: 0.1 }),
  onLeave: batch => gsap.to(batch, { opacity: 0, y: 20 }),
  onEnterBack: batch => gsap.to(batch, { opacity: 1, y: 0 }),
  onLeaveBack: batch => gsap.to(batch, { opacity: 0, y: -20 })
});

Common Patterns

Reveal on Scroll

gsap.utils.toArray('.reveal').forEach(elem => {
  gsap.from(elem, {
    opacity: 0,
    y: 50,
    duration: 0.8,
    scrollTrigger: {
      trigger: elem,
      start: 'top 80%',
      toggleActions: 'play none none none'
    }
  });
});

Progress Indicator

gsap.to('.progress-bar', {
  scaleX: 1,
  transformOrigin: 'left center',
  ease: 'none',
  scrollTrigger: {
    trigger: 'body',
    start: 'top top',
    end: 'bottom bottom',
    scrub: 0.3
  }
});

Sticky Header Transform

ScrollTrigger.create({
  start: 'top -80',
  onUpdate: (self) => {
    if (self.direction === 1) {
      gsap.to('.header', { y: -80, duration: 0.3 });
    } else {
      gsap.to('.header', { y: 0, duration: 0.3 });
    }
  }
});

Temporal Collapse Patterns

Countdown Scroll Reveal

// Reveal countdown sections as user scrolls
const sections = ['days', 'hours', 'minutes', 'seconds'];

sections.forEach((section, i) => {
  gsap.from(`.countdown-${section}`, {
    opacity: 0,
    scale: 0.8,
    y: 50,
    duration: 0.8,
    ease: 'power3.out',
    scrollTrigger: {
      trigger: `.countdown-${section}`,
      start: 'top 80%',
      toggleActions: 'play none none none'
    }
  });
});

Scroll-Based Time Dilation Effect

gsap.timeline({
  scrollTrigger: {
    trigger: '.time-section',
    start: 'top center',
    end: 'bottom center',
    scrub: 1
  }
})
.to('.time-digit', {
  textShadow: '0 0 50px #00F5FF, 0 0 100px #00F5FF',
  scale: 1.1
})
.to('.particles', {
  opacity: 1,
  filter: 'blur(0px)'
}, '<');

Performance Tips

// Disable on mobile if needed
ScrollTrigger.matchMedia({
  '(min-width: 768px)': function() {
    // Desktop animations
  },
  '(max-width: 767px)': function() {
    // Simpler mobile animations
  }
});

// Refresh on resize
ScrollTrigger.refresh();

// Kill all ScrollTriggers
ScrollTrigger.killAll();

// Kill specific trigger
const st = ScrollTrigger.create({ ... });
st.kill();

Reference

  • See gsap-fundamentals for animation basics
  • See gsap-sequencing for timeline composition
  • See gsap-react for React integration with ScrollTrigger