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.
Scroll-driven animations and interactions.
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
}
});
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'
}
});
// 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
| Value | Description |
|-------|-------------|
| top | Top edge |
| center | Center |
| bottom | Bottom edge |
| 80% | 80% from top |
| +=100 | Plus 100 pixels |
| -=50 | Minus 50 pixels |
// 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
}
});
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)
}
});
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 });
ScrollTrigger.create({
trigger: '.panel',
start: 'top top',
end: '+=500', // Pin for 500px of scroll
pin: true
});
gsap.to('.content', {
x: '-200%',
ease: 'none',
scrollTrigger: {
trigger: '.horizontal-section',
start: 'top top',
end: () => '+=' + document.querySelector('.horizontal-section').offsetWidth,
pin: true,
scrub: 1
}
});
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
});
// 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 | 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 |
ScrollTrigger.create({
trigger: '.sections',
start: 'top top',
end: 'bottom bottom',
snap: 1 / 4 // Snap to quarters
});
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: {
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
}
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')
});
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);
}
});
// 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
}
});
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
}
});
});
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
}
});
ScrollTrigger.create({
trigger: '.section',
start: 'top center',
end: 'bottom center',
markers: true, // Show visual markers
// markers: { startColor: 'green', endColor: 'red', fontSize: '12px' }
});
ScrollTrigger.batch('.card', {
onEnter: (elements) => {
gsap.from(elements, {
opacity: 0,
y: 50,
stagger: 0.1,
duration: 0.5
});
},
start: 'top 85%'
});
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 })
});
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'
}
});
});
gsap.to('.progress-bar', {
scaleX: 1,
transformOrigin: 'left center',
ease: 'none',
scrollTrigger: {
trigger: 'body',
start: 'top top',
end: 'bottom bottom',
scrub: 0.3
}
});
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 });
}
}
});
// 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'
}
});
});
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)'
}, '<');
// 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();
gsap-fundamentals for animation basicsgsap-sequencing for timeline compositiongsap-react for React integration with ScrollTriggernpx skills add Bbeierle12/gsap-scrolltrigger下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer