506 lines
9.8 KiB
Markdown
506 lines
9.8 KiB
Markdown
---
|
|
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
|