| name | gsap |
| description | GSAP animation engine sub-skill - core, timeline, ScrollTrigger, plugins. |
GSAP — Animation Engine
When to use GSAP
| Criteria | CSS Transitions | Framer Motion | GSAP |
|---|
| Hover / simple toggle | Yes | Yes | Overkill |
| Sequenced timeline | No | Limited | Yes |
| Scroll-driven | scroll-timeline | Limited | ScrollTrigger |
| Complex stagger | No | Basic | Distribution |
| Mobile perf (60fps) | Good | Average | Excellent |
| Text splitting | No | No | SplitText |
| SVG morph / draw | No | No | MorphSVG |
| Bundle size concern | 0kb | ~30kb | ~25kb + plugins |
Rule: if the animation needs timeline, scroll-link, or distributed stagger, use GSAP. Otherwise CSS first.
Setup
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(ScrollTrigger, SplitText);
React: use useGSAP() from the @gsap/react package instead of useEffect + manual cleanup.
import { useGSAP } from "@gsap/react";
useGSAP(() => {
gsap.to(".box", { x: 200 });
}, { scope: containerRef });
Core Patterns
defaults{} to avoid repetition
const tl = gsap.timeline({
defaults: { duration: 0.8, ease: "power2.out" },
});
tl.to(".a", { y: -20 })
.to(".b", { y: -20 }, "<0.1")
.to(".c", { y: -20 }, "<0.1");
fromTo for full control
gsap.fromTo(".card", { y: 40, opacity: 0 }, { y: 0, opacity: 1, stagger: 0.15 });
Stagger with distribution
gsap.to(".grid-item", {
scale: 0,
stagger: {
each: 0.05,
from: "center",
grid: "auto",
axis: "x",
},
});
ScrollTrigger Patterns
Basic Pin + Scrub
gsap.to(".panel", {
x: "-300%",
ease: "none",
scrollTrigger: {
trigger: ".container",
pin: true,
scrub: 1,
end: () => "+=" + document.querySelector(".container").scrollWidth,
},
});
Batch for mass reveal
ScrollTrigger.batch(".card", {
onEnter: (elements) => gsap.to(elements, { opacity: 1, y: 0, stagger: 0.1 }),
start: "top 85%",
});
Horizontal scroll with containerAnimation
const scrollTween = gsap.to(".panels", {
x: () => -(document.querySelector(".panels").scrollWidth - window.innerWidth),
ease: "none",
scrollTrigger: { trigger: ".wrapper", pin: true, scrub: 1 },
});
gsap.to(".panel-content", {
scale: 1.2,
scrollTrigger: {
trigger: ".panel-content",
containerAnimation: scrollTween,
start: "left center",
end: "right center",
scrub: true,
},
});
DO NOT — Critical mistakes
1. Ease on containerAnimation
scrollTrigger: { containerAnimation: scrollTween, scrub: 1, ease: "power2.out" }
const scrollTween = gsap.to(".panels", { x: ..., ease: "none", scrollTrigger: { scrub: 1 } });
2. ScrollTrigger on a child tween in a timeline
const tl = gsap.timeline({ scrollTrigger: { trigger: ".section" } });
tl.to(".box", { x: 100, scrollTrigger: { trigger: ".box" } });
gsap.to(".box", { x: 100, scrollTrigger: { trigger: ".box" } });
3. setState in onUpdate
scrollTrigger: { onUpdate: (self) => setProgress(self.progress) }
const progressRef = useRef(0);
scrollTrigger: { onUpdate: (self) => { progressRef.current = self.progress; } }
4. immediateRender on from() in a timeline
tl.to(".box", { x: 100 });
tl.from(".box", { y: 50 });
tl.to(".box", { x: 100 });
tl.from(".box", { y: 50, immediateRender: false });
5. Animating non-transform properties
gsap.to(".box", { width: 200, height: 200 });
gsap.to(".box", { scaleX: 1.5, scaleY: 1.5 });
Refs
references/core.md — Complete gsap.to/from/fromTo/set API, options
references/timeline.md — Timeline, position parameter, nesting
references/scrolltrigger.md — Full ScrollTrigger reference
references/plugins.md — SplitText, Flip, MorphSVG, DrawSVG, MotionPath, Observer