home / skills / bbeierle12 / skill-mcp-claude / gsap-scrolltrigger

gsap-scrolltrigger skill

/skills/gsap-scrolltrigger

This skill helps you create engaging scroll-driven animations using GSAP ScrollTrigger, enabling pinning, scrubbing, snapping and parallax effects.

npx playbooks add skill bbeierle12/skill-mcp-claude --skill gsap-scrolltrigger

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
9.8 KB
---
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

```bash
npm install gsap
```

```javascript
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

```javascript
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

```javascript
// 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

```javascript
// 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

```javascript
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

```javascript
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

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

### Pin with Animation

```javascript
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

```javascript
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

```javascript
// 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

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

### Snap to Labels

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
// 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

```javascript
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

```javascript
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)

```javascript
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

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

### Batch Configuration

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
// 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

```javascript
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

```javascript
// 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

Overview

This skill provides ready-to-use patterns and examples for building scroll-driven animations with GSAP's ScrollTrigger plugin. It covers scrubbing, pinning, snap points, parallax layers, horizontal scrolling, batch reveals, and progress indicators. Use it to turn scroll position into precise animation control and polished UX.

How this skill works

The skill maps scroll position to GSAP timelines and tweens via ScrollTrigger configurations (trigger, start/end, scrub, pin, snap). It includes code patterns for tying animation progress to scroll, pinning sections, snapping to labels or positions, and creating multi-layer parallax. It also shows callbacks, batch processing, markers for development, and responsive matchMedia handling for performance.

When to use it

  • Create scroll-tied animations whose progress follows the scrollbar (scrub).
  • Build sticky or pinned sections and horizontal scrolling galleries.
  • Implement snap-to-section navigation or timeline labels for guided scrolling experiences.
  • Add parallax effects with independent layer speeds for depth.
  • Stagger or batch reveal lists and cards on scroll for performant entry animations.
  • Show scroll progress indicators or sticky header transforms.

Best practices

  • Use scrub with a small numeric value for smooth eased follow, or true for instant mapping to scroll. Avoid excessive scrub smoothing on mobile.
  • Pin only what you need; use pinSpacing to prevent layout jumps and consider pinSpacing:false for intentional overlays.
  • Use ScrollTrigger.matchMedia to disable or simplify complex triggers on small screens.
  • Prefer timelines for multi-step sequences and add labels to enable snap-to-label behavior.
  • Batch similar elements with ScrollTrigger.batch to reduce layout thrash and improve performance.
  • Call ScrollTrigger.refresh() on major DOM changes and kill triggers when unmounting components.

Example use cases

  • A hero section that pins and scrubs a multi-step product reveal timeline.
  • A horizontal gallery that pins a container and translates panels using xPercent with snap to panels.
  • A multi-layer parallax landing page where background moves slower and foreground moves faster.
  • A progress bar that scales horizontally tracking page scroll progress.
  • Cards or list items that stagger into view using ScrollTrigger.batch for smooth entrance.

FAQ

How do I make an animation follow the scroll position exactly?

Set scrollTrigger.scrub to true to map animation progress directly to scroll. Use a numeric scrub value (e.g., 1) for smoothed follow with a lag effect.

How can I limit heavy animations on mobile?

Wrap triggers in ScrollTrigger.matchMedia and provide simpler or disabled animations for small viewports. Also reduce pinned areas and avoid large scrub distances on mobile.