Gsap Scroll Trigger
GSAP ScrollTrigger pattern for scroll-linked section reveals, pinned scrub timelines, and snap — with gsap.matchMedia() prefers-reduced-motion guard and useGSAP hook for React/Next.js.
$ prime install @community/template-gsap-scroll-trigger Projection
Always in _index.xml · the agent never has to ask for this.
GsapScrollTrigger [template] v1.0.0
GSAP ScrollTrigger pattern for scroll-linked section reveals, pinned scrub timelines, and snap — with gsap.matchMedia() prefers-reduced-motion guard and useGSAP hook for React/Next.js.
Loaded when retrieval picks the atom as adjacent / supporting.
GsapScrollTrigger [template] v1.0.0
GSAP ScrollTrigger pattern for scroll-linked section reveals, pinned scrub timelines, and snap — with gsap.matchMedia() prefers-reduced-motion guard and useGSAP hook for React/Next.js.
Language
javascript
Body
<script type="module">
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
// ── Pattern A: Section reveal on scroll ─────────────────────────
gsap.utils.toArray(".section").forEach((section) => {
gsap.from(section, {
y: 80,
opacity: 0,
duration: 0.8,
ease: "power2.out",
scrollTrigger: {
trigger: section,
start: "top 80%",
end: "top 20%",
toggleActions: "play none none reverse",
// markers: true, // debug only
},
});
});
// ── Pattern B: Scrubbed horizontal panel ─────────────────────────
const panels = gsap.utils.toArray(".panel");
gsap.to(".horizontal-panels", {
xPercent: -100 * (panels.length - 1),
ease: "none",
scrollTrigger: {
trigger: ".horizontal-container",
pin: true,
scrub: 1,
snap: 1 / (panels.length - 1),
end: () => "+=" + document.querySelector(".horizontal-container").offsetWidth,
},
});
// ── Pattern C: Progress bar ───────────────────────────────────────
gsap.to(".reading-progress", {
scaleX: 1,
ease: "none",
scrollTrigger: {
scrub: true,
start: "top top",
end: "bottom bottom",
},
});
});
mm.add("(prefers-reduced-motion: reduce)", () => {
// Instant reveal — no tweens, no ScrollTriggers
gsap.set(".section", { opacity: 1, y: 0 });
gsap.set(".reading-progress", { scaleX: 0 });
});
</script>
<!--
React / Next.js App Router (Client Component):
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
export function RevealSection({ children }) {
const container = useRef(null);
useGSAP(() => {
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(".reveal-item", {
y: 60, opacity: 0, duration: 0.8, ease: "power2.out",
stagger: 0.1,
scrollTrigger: {
trigger: container.current,
start: "top 75%",
toggleActions: "play none none reverse",
},
});
});
}, { scope: container });
return <div ref={container}>{children}</div>;
}
-->
Usage Notes
- Always use gsap.matchMedia() — not a raw if-check — so cleanup fires when preference changes at runtime.
- useGSAP + scope: container prevents selector leaks across component instances.
- scrub: 1 means 1 second of smoothing lag behind scroll position; scrub: true is instant.
- toggleActions: 'play none none reverse' replays the animation on re-scroll into view.
- markers: true is debug only — remove before production.
- CSS scroll-driven animations are simpler for basic reveals (no Firefox support as of 2026).
Accessibility
- gsap.matchMedia prefers-reduced-motion branch must instant-set elements to final state — not just skip tweens.
- Pinned sections can disorient keyboard users — always provide a skip/escape mechanism.
Loaded when retrieval picks the atom as a focal / direct hit.
GsapScrollTrigger [template] v1.0.0
GSAP ScrollTrigger pattern for scroll-linked section reveals, pinned scrub timelines, and snap — with gsap.matchMedia() prefers-reduced-motion guard and useGSAP hook for React/Next.js.
Language
javascript
Body
<script type="module">
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
// ── Pattern A: Section reveal on scroll ─────────────────────────
gsap.utils.toArray(".section").forEach((section) => {
gsap.from(section, {
y: 80,
opacity: 0,
duration: 0.8,
ease: "power2.out",
scrollTrigger: {
trigger: section,
start: "top 80%",
end: "top 20%",
toggleActions: "play none none reverse",
// markers: true, // debug only
},
});
});
// ── Pattern B: Scrubbed horizontal panel ─────────────────────────
const panels = gsap.utils.toArray(".panel");
gsap.to(".horizontal-panels", {
xPercent: -100 * (panels.length - 1),
ease: "none",
scrollTrigger: {
trigger: ".horizontal-container",
pin: true,
scrub: 1,
snap: 1 / (panels.length - 1),
end: () => "+=" + document.querySelector(".horizontal-container").offsetWidth,
},
});
// ── Pattern C: Progress bar ───────────────────────────────────────
gsap.to(".reading-progress", {
scaleX: 1,
ease: "none",
scrollTrigger: {
scrub: true,
start: "top top",
end: "bottom bottom",
},
});
});
mm.add("(prefers-reduced-motion: reduce)", () => {
// Instant reveal — no tweens, no ScrollTriggers
gsap.set(".section", { opacity: 1, y: 0 });
gsap.set(".reading-progress", { scaleX: 0 });
});
</script>
<!--
React / Next.js App Router (Client Component):
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
export function RevealSection({ children }) {
const container = useRef(null);
useGSAP(() => {
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(".reveal-item", {
y: 60, opacity: 0, duration: 0.8, ease: "power2.out",
stagger: 0.1,
scrollTrigger: {
trigger: container.current,
start: "top 75%",
toggleActions: "play none none reverse",
},
});
});
}, { scope: container });
return <div ref={container}>{children}</div>;
}
-->
Usage Notes
- Always use gsap.matchMedia() — not a raw if-check — so cleanup fires when preference changes at runtime.
- useGSAP + scope: container prevents selector leaks across component instances.
- scrub: 1 means 1 second of smoothing lag behind scroll position; scrub: true is instant.
- toggleActions: 'play none none reverse' replays the animation on re-scroll into view.
- markers: true is debug only — remove before production.
- CSS scroll-driven animations are simpler for basic reveals (no Firefox support as of 2026).
Accessibility
- gsap.matchMedia prefers-reduced-motion branch must instant-set elements to final state — not just skip tweens.
- Pinned sections can disorient keyboard users — always provide a skip/escape mechanism.
Source
prime-system/examples/frontend-design/primes/compiled/@community/template-gsap-scroll-trigger/atom.yaml