# GSAP Plugins

> **Scope:** frontend-nextjs-gsap
> **Layer:** 3
> **Keywords:** flip, splittext, morphsvg, drawsvg, motionpath, draggable, observer, scrollto, gsap-plugins
> **Load When:** ui-designer active, or the project animates with GSAP

**Verified against:** GSAP 3.13 + @gsap/react. Last-verified: 2026-05-20.

---

Reference guide for GSAP's plugin ecosystem in Next.js. Covers the full plugin set — Flip, Observer, ScrollToPlugin, SplitText, MorphSVG, DrawSVG, MotionPath, Draggable, InertiaPlugin.

## Plugin Registration & Licensing

Since the Webflow acquisition, **GSAP and every plugin are free for commercial
use** — no Club GreenSock membership, no license key, no auth token, no private
registry. The formerly-premium plugins (SplitText, MorphSVG, DrawSVG, MotionPath,
Draggable, InertiaPlugin, ScrambleText, CustomEase, CustomWiggle, CustomBounce,
Physics2D, PhysicsProps) now ship in the public `gsap` package alongside Flip,
Observer, and ScrollToPlugin.

```bash
# Everything — core + all plugins — installs from the public package
npm install gsap @gsap/react
```

> Disregard any older instruction to install `gsap@npm:@gsap/shockingly` or
> configure a private npm registry — that path is obsolete.

Registration pattern (do ONCE at app init):

```typescript
'use client';
import gsap from 'gsap';
import { Flip } from 'gsap/Flip';
import { SplitText } from 'gsap/SplitText';
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';

gsap.registerPlugin(Flip, SplitText, ScrollToPlugin);
```

## Flip Plugin (Free)

Layout transition animations using the FLIP technique (First, Last, Invert, Play):

```typescript
'use client';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { Flip } from 'gsap/Flip';

function FilterableGrid({ items, filter }: { items: Item[]; filter: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  const applyFilter = (newFilter: string) => {
    // 1. Capture current state
    const state = Flip.getState('.grid-item');

    // 2. Make DOM changes (reorder, show/hide)
    setFilter(newFilter);

    // 3. Animate from old positions to new
    Flip.from(state, {
      duration: 0.6,
      ease: 'power2.out',
      stagger: 0.04,
      absolute: true,       // prevents layout jumping during animation
      onEnter: (elements) => gsap.fromTo(elements, { opacity: 0, scale: 0.8 }, { opacity: 1, scale: 1, duration: 0.4 }),
      onLeave: (elements) => gsap.to(elements, { opacity: 0, scale: 0.8, duration: 0.3 }),
    });
  };
}
```

Use cases: filterable grids, list reordering, expanding cards, shared element transitions.

## SplitText

Split text into chars, words, or lines for staggered reveals:

```typescript
useGSAP(() => {
  const split = SplitText.create('.hero-title', {
    type: 'chars,words',
    autoSplit: true,         // re-splits on resize
    onSplit: () => {
      // Animate after split is ready
      gsap.from(split.chars, {
        y: 30,
        opacity: 0,
        stagger: 0.03,
        duration: 0.5,
        ease: 'power2.out',
      });
    },
  });

  // Cleanup: revert split on unmount (handled by useGSAP context)
}, { scope: containerRef });
```

Key points:
- Use `autoSplit: true` for responsive -- re-splits when container resizes
- Use `onSplit` callback to animate AFTER split completes
- The split creates `<div>` wrappers -- this affects layout. Test carefully.
- useGSAP automatically reverts the split on unmount

## MorphSVG

Morph between SVG shapes:

```typescript
gsap.to('#circle', {
  morphSVG: {
    shape: '#star',
    type: 'rotational',    // smoother for complex shapes
    shapeIndex: 'auto',     // auto-aligns anchor points
  },
  duration: 1.2,
  ease: 'power2.inOut',
});

// Convert non-path SVG elements to paths first
MorphSVGPlugin.convertToPath('circle, rect, ellipse, polygon, polyline, line');
```

Rules:
- Both shapes must be `<path>` elements (use convertToPath for others)
- Match point counts for smoother morphs
- `type: "rotational"` is smoother for most shapes

## DrawSVG

Animate SVG stroke drawing:

```typescript
// Draw stroke from 0% to 100%
gsap.fromTo('.svg-path',
  { drawSVG: '0%' },
  { drawSVG: '100%', duration: 2, ease: 'power2.inOut' }
);

// Partial draws
gsap.to('.line', { drawSVG: '20% 80%', duration: 1 }); // middle section only

// Line drawing reveal pattern
const paths = gsap.utils.toArray<SVGPathElement>('.icon path');
gsap.from(paths, {
  drawSVG: '0%',
  stagger: 0.2,
  duration: 1,
  ease: 'power1.out',
});
```

Requires SVG elements with visible `stroke` and `stroke-width`. Does not work on `fill`.

## MotionPath

Animate elements along SVG paths:

```typescript
gsap.to('.rocket', {
  motionPath: {
    path: '#flight-path',
    align: '#flight-path',
    autoRotate: true,        // element rotates to follow path direction
    alignOrigin: [0.5, 0.5], // center of element on path
  },
  duration: 3,
  ease: 'power1.inOut',
});
```

Use MotionPathHelper (dev tool) to visually edit paths: `MotionPathHelper.create('.rocket')`.

## ScrollToPlugin (Free)

Smooth scroll to elements or positions:

```typescript
// Scroll to element
gsap.to(window, {
  scrollTo: { y: '#contact-section', offsetY: 80 }, // 80px offset for header
  duration: 1,
  ease: 'power2.inOut',
});

// Scroll to position
gsap.to(window, { scrollTo: { y: 500 }, duration: 0.8 });

// Auto-kill on user scroll (prevents fighting user input)
gsap.to(window, {
  scrollTo: { y: '#section', autoKill: true },
  duration: 1.5,
});
```

## Draggable

Make elements draggable with optional physics:

```typescript
'use client';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { Draggable } from 'gsap/Draggable';

function DraggableCard() {
  const cardRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    Draggable.create(cardRef.current, {
      type: 'x,y',
      bounds: '.container',
      edgeResistance: 0.65,
      throwProps: true,        // momentum (requires InertiaPlugin)
      snap: {
        x: (value) => Math.round(value / 100) * 100, // snap to 100px grid
        y: (value) => Math.round(value / 100) * 100,
      },
      onDrag() { /* called every frame during drag */ },
      onDragEnd() { /* called when drag completes */ },
    });
  }, { scope: cardRef });
}
```

## Observer (Free)

Normalize pointer, scroll, touch, and wheel events:

```typescript
import { Observer } from 'gsap/Observer';

Observer.create({
  target: window,
  type: 'wheel,touch,pointer',
  onUp: () => goToSection(currentIndex - 1),
  onDown: () => goToSection(currentIndex + 1),
  tolerance: 10,           // minimum distance before triggering
  preventDefault: true,     // prevent native scroll
  wheelSpeed: -1,          // invert direction
});
```

Use cases: custom scroll-jacking, swipe detection, directional gestures, fullpage scroll experiences.

## Combining Plugins — Real-World Recipes

**Recipe 1: Scroll-triggered text reveal**

```typescript
useGSAP(() => {
  const split = SplitText.create('.reveal-text', { type: 'words', autoSplit: true });

  gsap.from(split.words, {
    y: 20,
    opacity: 0,
    stagger: 0.05,
    duration: 0.5,
    ease: 'power2.out',
    scrollTrigger: {
      trigger: '.reveal-text',
      start: 'top 80%',
      toggleActions: 'play none none none',
    },
  });
}, { scope: containerRef });
```

**Recipe 2: Filterable grid with Flip + ScrollTrigger.refresh()**

```typescript
const handleFilter = (category: string) => {
  const state = Flip.getState('.item');
  setActiveCategory(category);

  Flip.from(state, {
    duration: 0.5,
    stagger: 0.03,
    absolute: true,
    onComplete: () => ScrollTrigger.refresh(), // recalculate positions after layout change
  });
};
```

**Recipe 3: SVG icon draw + morph sequence**

```typescript
const tl = gsap.timeline({ scrollTrigger: { trigger: '.icon-section', start: 'top center' } });
tl.from('.icon path', { drawSVG: '0%', stagger: 0.1, duration: 0.8 })
  .to('.icon path', { morphSVG: '#final-shape', duration: 1, ease: 'power2.inOut' }, '+=0.3');
```

---

*MORPH-SPEC by Polymorphism Tech*
