GSAP
GreenSock Animation Platform — timeline orchestration, ScrollTrigger, tweens, stagger, React integration with useGSAP, SplitText, morphSVG, and from/to/fromTo patterns.
GSAP is an imperative animation engine built for precision. Where declarative libraries describe end-states, GSAP gives you frame-level control over sequencing, easing, and playback. Its timeline model lets you compose animations like tracks on a video editor — with labels, offsets, and scrubbing.
## Key Points
- `"+=0.5"` — 0.5s after the previous tween ends
- `"-=0.3"` — 0.3s before the previous tween ends (overlap)
- `"<"` — same start time as previous tween
- `"<+=0.2"` — 0.2s after the previous tween starts
- `2` — absolute 2s from timeline start
- **Always use `useGSAP` in React.** It handles cleanup automatically, preventing memory leaks and orphaned animations on unmount.
- **Set `scope` on `useGSAP`.** Scoping limits selector queries (like `.box`) to descendants of the ref, preventing collisions with other component instances.
- **Use `defaults` on timelines.** `gsap.timeline({ defaults: { duration: 0.5, ease: "power2.out" } })` reduces repetition across child tweens.
- **Prefer `gsap.context()` outside React** for vanilla JS cleanup. Call `ctx.revert()` to undo all animations in the context.
- **Use `toggleActions` for scroll-triggered entry animations** and `scrub` for scroll-linked progress animations. Do not mix them on the same trigger.
- **Call `ScrollTrigger.refresh()` after layout changes** (e.g., after images load or accordion expand) so trigger positions recalculate.
- **Use `will-change: transform` on animated elements** to promote them to GPU layers and avoid paint jank.
## Quick Example
```bash
npm install gsap
```
```bash
npm install @gsap/react
```skilldb get animation-services-skills/GSAPFull skill: 254 linesGSAP (GreenSock Animation Platform)
Core Philosophy
GSAP is an imperative animation engine built for precision. Where declarative libraries describe end-states, GSAP gives you frame-level control over sequencing, easing, and playback. Its timeline model lets you compose animations like tracks on a video editor — with labels, offsets, and scrubbing.
Key mental model: build a timeline, add tweens at specific positions, play/reverse/seek at will. GSAP works on any numeric property of any JavaScript object, not just DOM elements.
Setup
npm install gsap
For React projects, install the official hook:
npm install @gsap/react
Register plugins once at app entry:
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useGSAP } from "@gsap/react";
gsap.registerPlugin(ScrollTrigger);
Minimal Example
import { useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
export function FadeIn({ children }: { children: React.ReactNode }) {
const containerRef = useRef<HTMLDivElement>(null);
useGSAP(() => {
gsap.from(containerRef.current, {
opacity: 0,
y: 30,
duration: 0.6,
ease: "power2.out",
});
}, { scope: containerRef });
return <div ref={containerRef}>{children}</div>;
}
Key Techniques
Tween Types: to, from, fromTo
// Animate FROM current state TO target
gsap.to(".box", { x: 200, rotation: 360, duration: 1 });
// Animate FROM defined state TO current state (good for intro animations)
gsap.from(".box", { opacity: 0, y: 50, duration: 0.8 });
// Full control over start and end
gsap.fromTo(".box",
{ opacity: 0, scale: 0.8 },
{ opacity: 1, scale: 1, duration: 0.5, ease: "back.out(1.7)" }
);
Timeline Orchestration
Timelines chain tweens with precise positioning. The position parameter controls when each tween starts.
import { useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
export function HeroAnimation() {
const containerRef = useRef<HTMLDivElement>(null);
useGSAP(() => {
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.from(".hero-title", { opacity: 0, y: 60, duration: 0.8 })
.from(".hero-subtitle", { opacity: 0, y: 40, duration: 0.6 }, "-=0.4") // overlap
.from(".hero-cta", { opacity: 0, scale: 0.9, duration: 0.5 }, "-=0.2")
.from(".hero-image", { opacity: 0, x: 80, duration: 1 }, "<"); // same start as previous
}, { scope: containerRef });
return (
<div ref={containerRef}>
<h1 className="hero-title">Welcome</h1>
<p className="hero-subtitle">Build something great</p>
<button className="hero-cta">Get Started</button>
<img className="hero-image" src="/hero.png" alt="" />
</div>
);
}
Position parameter cheat sheet:
"+=0.5"— 0.5s after the previous tween ends"-=0.3"— 0.3s before the previous tween ends (overlap)"<"— same start time as previous tween"<+=0.2"— 0.2s after the previous tween starts2— absolute 2s from timeline start
Stagger
useGSAP(() => {
gsap.from(".card", {
opacity: 0,
y: 40,
duration: 0.5,
stagger: {
each: 0.1,
from: "start", // "center", "end", "edges", "random"
ease: "power1.in",
},
});
}, { scope: containerRef });
ScrollTrigger
useGSAP(() => {
gsap.from(".feature-section", {
scrollTrigger: {
trigger: ".feature-section",
start: "top 80%",
end: "top 30%",
toggleActions: "play none none reverse",
// scrub: true, // uncomment to tie progress to scroll position
},
opacity: 0,
y: 60,
duration: 0.8,
});
}, { scope: containerRef });
Pin and Scrub Pattern
useGSAP(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".pinned-section",
start: "top top",
end: "+=2000",
pin: true,
scrub: 1,
},
});
tl.to(".step-1", { opacity: 0, y: -50 })
.from(".step-2", { opacity: 0, y: 50 })
.to(".step-2", { opacity: 0, y: -50 })
.from(".step-3", { opacity: 0, y: 50 });
}, { scope: containerRef });
useGSAP — React Integration
useGSAP is the official replacement for manually managing useEffect + useRef cleanup. It automatically reverts all GSAP animations created inside its callback when the component unmounts.
import { useGSAP } from "@gsap/react";
import { gsap } from "gsap";
export function AnimatedComponent({ isActive }: { isActive: boolean }) {
const containerRef = useRef<HTMLDivElement>(null);
useGSAP(() => {
if (isActive) {
gsap.to(".indicator", { scale: 1.2, duration: 0.3 });
} else {
gsap.to(".indicator", { scale: 1, duration: 0.3 });
}
}, { dependencies: [isActive], scope: containerRef });
return (
<div ref={containerRef}>
<div className="indicator" />
</div>
);
}
SplitText (Club Plugin)
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(SplitText);
useGSAP(() => {
const split = new SplitText(".headline", { type: "chars,words" });
gsap.from(split.chars, {
opacity: 0,
y: 20,
rotateX: -90,
stagger: 0.03,
duration: 0.6,
ease: "back.out(1.7)",
});
}, { scope: containerRef });
MorphSVG (Club Plugin)
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";
gsap.registerPlugin(MorphSVGPlugin);
useGSAP(() => {
gsap.to("#circle", {
morphSVG: "#star",
duration: 1.5,
ease: "elastic.out(1, 0.5)",
});
}, { scope: containerRef });
Best Practices
- Always use
useGSAPin React. It handles cleanup automatically, preventing memory leaks and orphaned animations on unmount. - Set
scopeonuseGSAP. Scoping limits selector queries (like.box) to descendants of the ref, preventing collisions with other component instances. - Use
defaultson timelines.gsap.timeline({ defaults: { duration: 0.5, ease: "power2.out" } })reduces repetition across child tweens. - Prefer
gsap.context()outside React for vanilla JS cleanup. Callctx.revert()to undo all animations in the context. - Use
toggleActionsfor scroll-triggered entry animations andscrubfor scroll-linked progress animations. Do not mix them on the same trigger. - Call
ScrollTrigger.refresh()after layout changes (e.g., after images load or accordion expand) so trigger positions recalculate. - Use
will-change: transformon animated elements to promote them to GPU layers and avoid paint jank.
Anti-Patterns
- Using
useEffectinstead ofuseGSAP. Manual cleanup withuseEffectis error-prone. Forgetting to kill tweens causes animations to target unmounted DOM nodes. - Creating tweens without refs or scoped selectors. Bare selectors like
gsap.to(".card", ...)in a component will animate every.cardon the page, not just the one in your component. - Animating layout properties (width, height, top, left). Stick to transforms (
x,y,scale,rotation) andopacityfor 60fps performance. - Nesting ScrollTriggers without
invalidateOnRefresh. When the page layout shifts, nested triggers can fall out of sync. SetinvalidateOnRefresh: trueon dynamic content. - Calling
gsap.registerPlugininside components. Register plugins once at the app entry point, not inside render functions. - Forgetting
overwriteon rapid-fire triggers. If a user can trigger the same animation repeatedly, setoverwrite: "auto"oroverwrite: trueto prevent tween stacking.
Install this skill directly: skilldb add animation-services-skills
Related Skills
Anime.js
Lightweight JavaScript animation engine — DOM, CSS, SVG, and object property animations with timeline sequencing, staggering, and spring-based easing.
Auto Animate
Zero-config animation library by FormKit — automatic transitions for DOM additions, removals, and reordering with a single function call or directive.
Framer Motion (Motion)
Production-grade animation library for React — animate, variants, AnimatePresence, layout animations, gestures, scroll-triggered effects, useMotionValue, and stagger orchestration.
Lottie
Render After Effects animations on the web with lottie-react and lottie-web — player controls, interactivity, lazy loading, and the light player for optimized bundles.
Motion One
Lightweight Web Animations API wrapper — animate, timeline, spring physics, scroll-linked animations, stagger, and minimal bundle size for modern browsers.
Popmotion
Functional animation library — spring physics, decay, keyframe interpolation, and composable animation primitives that power Framer Motion's internals.