GSAP ScrollTrigger
Scroll-driven animations and interactions.
Quick Start
bash
npm install gsapjavascript
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 centerstart: 'top 80%' // Trigger's top hits 80% down viewportstart: 'center center' // Trigger's center hits viewport centerstart: 'bottom top' // Trigger's bottom hits viewport topstart: 'top top+=100' // Trigger's top hits 100px below viewport topPosition 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 positiongsap.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 oncetoggleActions: 'play reverse play reverse' // Toggle directiontoggleActions: 'restart none none none' // Restart each timetoggleActions: '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 scrollgsap.to('.background', { yPercent: -50, ease: 'none', scrollTrigger: { trigger: '.section', scrub: true }});
// Foreground moves fastergsap.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 scrollsconst 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 neededScrollTrigger.matchMedia({ '(min-width: 768px)': function() { // Desktop animations }, '(max-width: 767px)': function() { // Simpler mobile animations }});
// Refresh on resizeScrollTrigger.refresh();
// Kill all ScrollTriggersScrollTrigger.killAll();
// Kill specific triggerconst st = ScrollTrigger.create({ ... });st.kill();Reference
- See
gsap-fundamentalsfor animation basics - See
gsap-sequencingfor timeline composition - See
gsap-reactfor React integration with ScrollTrigger


