Skip to main content
Technology & EngineeringAnimation Services254 lines

GSAP

GreenSock Animation Platform — timeline orchestration, ScrollTrigger, tweens, stagger, React integration with useGSAP, SplitText, morphSVG, and from/to/fromTo patterns.

Quick Summary28 lines
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 lines
Paste into your CLAUDE.md or agent config

GSAP (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 starts
  • 2 — 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 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.

Anti-Patterns

  • Using useEffect instead of useGSAP. Manual cleanup with useEffect is 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 .card on the page, not just the one in your component.
  • Animating layout properties (width, height, top, left). Stick to transforms (x, y, scale, rotation) and opacity for 60fps performance.
  • Nesting ScrollTriggers without invalidateOnRefresh. When the page layout shifts, nested triggers can fall out of sync. Set invalidateOnRefresh: true on dynamic content.
  • Calling gsap.registerPlugin inside components. Register plugins once at the app entry point, not inside render functions.
  • Forgetting overwrite on rapid-fire triggers. If a user can trigger the same animation repeatedly, set overwrite: "auto" or overwrite: true to prevent tween stacking.

Install this skill directly: skilldb add animation-services-skills

Get CLI access →