Skip to content
Back to skills

Gsap Patterns

ASecurity

GSAP mastery — ScrollTrigger, timeline sequencing, SVG morphing, text animation, MotionPath, performance optimization.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 1, 2026
ai-agentsgoreactvuespringawsperformance

Security analysis

A100/100

Scanned June 1, 2026

npx -y skills add KevinZai/commander --skill gsap-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gsap Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gsap Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kevinzai-gsap-patterns/badge)](https://www.skillsdirectory.com/skills/kevinzai-gsap-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: GSAP Patterns
description: "GSAP mastery — ScrollTrigger, timeline sequencing, SVG morphing, text animation, MotionPath, performance optimization."
version: 1.0.0
category: animation
parent: ccc-design
---

# GSAP Patterns

Production-ready GSAP patterns for scroll-driven animations, timeline orchestration, SVG morphing, and text effects. Covers free core + premium plugins.

## Core Principles

- Register plugins once at app level, not per-component
- Always clean up animations in React `useEffect` return / Vue `onUnmounted`
- Use `gsap.context()` for scoped cleanup in React components
- Prefer `gsap.to()` over `gsap.from()` — "to" is more predictable
- ScrollTrigger: always call `ScrollTrigger.refresh()` after layout changes

## Setup

```tsx
// lib/gsap.ts — register once, import everywhere
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { TextPlugin } from "gsap/TextPlugin";

gsap.registerPlugin(ScrollTrigger, TextPlugin);

export { gsap, ScrollTrigger };
```

## Pattern Library

### 1. ScrollTrigger — Reveal on Scroll

```tsx
function ScrollRevealSection() {
  const sectionRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      gsap.from(".reveal-item", {
        y: 60,
        opacity: 0,
        duration: 0.8,
        ease: "power3.out",
        stagger: 0.15,
        scrollTrigger: {
          trigger: sectionRef.current,
          start: "top 80%",
          end: "bottom 20%",
          toggleActions: "play none none reverse",
        },
      });
    }, sectionRef);

    return () => ctx.revert();
  }, []);

  return (
    <section ref={sectionRef}>
      <h2 className="reveal-item">Title</h2>
      <p className="reveal-item">Description</p>
      <div className="reveal-item">Content</div>
    </section>
  );
}
```

### 2. ScrollTrigger — Pinned Section with Scrub

```tsx
function PinnedHero() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: containerRef.current,
          start: "top top",
          end: "+=200%",
          pin: true,
          scrub: 1,
          snap: { snapTo: 1 / 3, duration: 0.3, ease: "power2.inOut" },
        },
      });

      tl.to(".hero-title", { scale: 0.8, opacity: 0, duration: 1 })
        .to(".hero-subtitle", { y: -100, opacity: 1, duration: 1 }, "-=0.5")
        .to(".hero-cta", { scale: 1, opacity: 1, duration: 1 }, "-=0.3");
    }, containerRef);

    return () => ctx.revert();
  }, []);

  return (
    <div ref={containerRef} className="h-screen relative overflow-hidden">
      <h1 className="hero-title">Welcome</h1>
      <p className="hero-subtitle opacity-0">Build something great</p>
      <button className="hero-cta opacity-0 scale-75">Get Started</button>
    </div>
  );
}
```

### 3. Timeline Sequencing

```tsx
function AnimatedEntrance() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const tl = gsap.timeline({ defaults: { ease: "power3.out", duration: 0.6 } });

      tl.from(".logo", { scale: 0, rotation: -180, duration: 0.8 })
        .from(".nav-item", { y: -30, opacity: 0, stagger: 0.1 }, "-=0.4")
        .from(".hero-heading", { x: -60, opacity: 0 }, "-=0.3")
        .from(".hero-body", { x: -40, opacity: 0 }, "-=0.2")
        .from(".hero-button", { scale: 0.8, opacity: 0 }, "-=0.1")
        .from(".hero-image", { x: 60, opacity: 0, duration: 0.8 }, "-=0.4");
    }, containerRef);

    return () => ctx.revert();
  }, []);

  return <div ref={containerRef}>{/* ... */}</div>;
}
```

### 4. Horizontal Scroll Gallery

```tsx
function HorizontalScroll() {
  const containerRef = useRef<HTMLDivElement>(null);
  const panelsRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const panels = gsap.utils.toArray<HTMLElement>(".panel");

      gsap.to(panels, {
        xPercent: -100 * (panels.length - 1),
        ease: "none",
        scrollTrigger: {
          trigger: containerRef.current,
          pin: true,
          scrub: 1,
          snap: 1 / (panels.length - 1),
          end: () => "+=" + (panelsRef.current?.scrollWidth ?? 0),
        },
      });
    }, containerRef);

    return () => ctx.revert();
  }, []);

  return (
    <div ref={containerRef}>
      <div ref={panelsRef} className="flex">
        {/* .panel elements */}
      </div>
    </div>
  );
}
```

### 5. SVG Path Drawing

```tsx
function DrawSVGOnScroll() {
  const svgRef = useRef<SVGSVGElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const path = svgRef.current?.querySelector("path");
      if (!path) return;

      const length = path.getTotalLength();
      gsap.set(path, { strokeDasharray: length, strokeDashoffset: length });

      gsap.to(path, {
        strokeDashoffset: 0,
        ease: "none",
        scrollTrigger: {
          trigger: svgRef.current,
          start: "top center",
          end: "bottom center",
          scrub: 1,
        },
      });
    }, svgRef);

    return () => ctx.revert();
  }, []);

  return (
    <svg ref={svgRef} viewBox="0 0 800 400">
      <path d="M10 200 Q200 10 400 200 Q600 390 790 200" fill="none" stroke="currentColor" strokeWidth="2" />
    </svg>
  );
}
```

### 6. Text Split Animation

```tsx
function SplitTextReveal({ text }: { text: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const chars = gsap.utils.toArray<HTMLElement>(".char");

      gsap.from(chars, {
        y: 40,
        opacity: 0,
        rotationX: -90,
        stagger: 0.02,
        duration: 0.6,
        ease: "back.out(1.7)",
        scrollTrigger: {
          trigger: containerRef.current,
          start: "top 85%",
        },
      });
    }, containerRef);

    return () => ctx.revert();
  }, []);

  return (
    <div ref={containerRef}>
      {text.split("").map((char, i) => (
        <span key={i} className="char inline-block">
          {char === " " ? "\u00A0" : char}
        </span>
      ))}
    </div>
  );
}
```

## ScrollTrigger Configuration Reference

| Property | Values | Use Case |
|----------|--------|----------|
| `start` | `"top center"`, `"top 80%"`, `"top top"` | When trigger enters viewport |
| `end` | `"bottom center"`, `"+=200%"`, `"bottom top"` | When trigger exits viewport |
| `scrub` | `true`, `1` (smooth), `0.5` (snappier) | Link animation to scroll position |
| `pin` | `true`, element | Pin trigger element during animation |
| `snap` | `1/3`, `{ snapTo: "labels" }` | Snap to positions on scroll release |
| `toggleActions` | `"play pause resume reverse"` | Enter, leave, enter-back, leave-back |
| `markers` | `true` | Debug only — shows trigger positions |

## Easing Reference

| Ease | Character | Best For |
|------|-----------|----------|
| `"power2.out"` | Smooth deceleration | General UI transitions |
| `"power3.out"` | Confident stop | Hero entrances, reveals |
| `"power4.inOut"` | Dramatic in/out | Page transitions, modals |
| `"back.out(1.7)"` | Slight overshoot | Playful entrances, badges |
| `"elastic.out(1, 0.3)"` | Bouncy spring | Attention-grabbing elements |
| `"none"` | Linear | Scroll-scrubbed animations |

## Performance

- Use `will-change: transform` only during animation (GSAP handles this)
- Prefer `transform` and `opacity` — avoid animating layout properties
- Use `gsap.ticker` for frame-synced custom logic instead of `requestAnimationFrame`
- Call `ScrollTrigger.refresh()` after dynamic content loads or layout shifts
- Use `fastScrollEnd: true` on ScrollTrigger for mobile performance
- Set `anticipatePin: 1` to prevent pin jump flicker

## Cleanup Pattern (React)

```tsx
// Always use gsap.context for React components
useEffect(() => {
  const ctx = gsap.context(() => {
    // All GSAP code here — automatically scoped to ref
  }, containerRef);

  // Single cleanup call reverts everything
  return () => ctx.revert();
}, []);
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.