gsap-react

GSAP integration for React with lifecycle-aware hooks, ref patterns, and automatic cleanup. useGSAP hook handles component lifecycle, scoped selectors, dependency tracking, and automatic animation cleanup on unmount Supports single and multiple element refs, dynamic lists, and timeline management with play/pause/restart controls contextSafe wrapper for event handlers ensures animations respect React's context and prevent memory leaks Includes ScrollTrigger integration, custom animation hooks (useFadeIn, useHoverAnimation), and performance patterns like batching and will-change optimization

INSTALLATION
npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill gsap-react
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

useGSAP(() => {

gsap.to('.box', { x: 200, duration: 1 });

}, { scope: containerRef });

return (

Animated

);

}

## useGSAP Hook

### Basic Usage

import { useGSAP } from '@gsap/react';

import gsap from 'gsap';

function AnimatedComponent() {

const container = useRef(null);

useGSAP(() => {

// All GSAP animations here

gsap.from('.item', {

opacity: 0,

y: 50,

stagger: 0.1

});

}, { scope: container }); // Scope limits selector queries

return (

<div ref={container}>

<div className="item">Item 1</div>

<div className="item">Item 2</div>

<div className="item">Item 3</div>

</div>

);

}


### With Dependencies

function AnimatedComponent({ isOpen }) {

const container = useRef(null);

useGSAP(() => {

gsap.to('.drawer', {

height: isOpen ? 'auto' : 0,

duration: 0.3

});

}, { scope: container, dependencies: [isOpen] });

return (

<div ref={container}>

<div className="drawer">Content</div>

</div>

);

}


### Returning Context

function Component() {

const container = useRef(null);

const { context, contextSafe } = useGSAP(() => {

gsap.to('.box', { x: 200 });

}, { scope: container });

// Use contextSafe for event handlers

const handleClick = contextSafe(() => {

gsap.to('.box', { rotation: 360 });

});

return (

<div ref={container}>

<div className="box" onClick={handleClick}>Click me</div>

</div>

);

}


## Ref Patterns

### Single Element Ref

function SingleElement() {

const boxRef = useRef(null);

useGSAP(() => {

gsap.to(boxRef.current, {

x: 200,

rotation: 360,

duration: 1

});

});

return <div ref={boxRef}>Box</div>;

}


### Multiple Element Refs

function MultipleElements() {

const itemsRef = useRef([]);

useGSAP(() => {

gsap.from(itemsRef.current, {

opacity: 0,

y: 30,

stagger: 0.1

});

});

return (

<div>

{[1, 2, 3].map((item, i) => (

<div

key={item}

ref={el => itemsRef.current[i] = el}

>

Item {item}

</div>

))}

</div>

);

}


### Dynamic Refs

function DynamicList({ items }) {

const itemsRef = useRef(new Map());

useGSAP(() => {

gsap.from(Array.from(itemsRef.current.values()), {

opacity: 0,

y: 20,

stagger: 0.05

});

}, { dependencies: [items.length] });

return (

<div>

{items.map(item => (

<div

key={item.id}

ref={el => {

if (el) itemsRef.current.set(item.id, el);

else itemsRef.current.delete(item.id);

}}

>

{item.name}

</div>

))}

</div>

);

}


## Context and Cleanup

### Automatic Cleanup

// useGSAP automatically cleans up animations on unmount

function Component() {

useGSAP(() => {

// This timeline is automatically killed on unmount

gsap.timeline()

.to('.a', { x: 100 })

.to('.b', { x: 100 });

});

}


### Manual Context (Without useGSAP)

import gsap from 'gsap';

function Component() {

useEffect(() => {

const ctx = gsap.context(() => {

gsap.to('.box', { x: 200 });

gsap.to('.circle', { rotation: 360 });

});

return () => ctx.revert(); // Cleanup

}, []);

}


### Scoped Context

function Component() {

const containerRef = useRef(null);

useEffect(() => {

const ctx = gsap.context(() => {

// Selectors only query within containerRef

gsap.to('.item', { opacity: 1 });

}, containerRef);

return () => ctx.revert();

}, []);

}


## Event Handlers

### contextSafe for Events

function InteractiveComponent() {

const container = useRef(null);

const { contextSafe } = useGSAP(() => {

// Initial animation

gsap.set('.box', { scale: 1 });

}, { scope: container });

const handleMouseEnter = contextSafe(() => {

gsap.to('.box', { scale: 1.1, duration: 0.2 });

});

const handleMouseLeave = contextSafe(() => {

gsap.to('.box', { scale: 1, duration: 0.2 });

});

return (

<div ref={container}>

<div

className="box"

onMouseEnter={handleMouseEnter}

onMouseLeave={handleMouseLeave}

>

Hover me

</div>

</div>

);

}


### useCallback Alternative

function Component() {

const boxRef = useRef(null);

const tweenRef = useRef(null);

const animateBox = useCallback(() => {

tweenRef.current?.kill();

tweenRef.current = gsap.to(boxRef.current, {

x: '+=50',

duration: 0.3

});

}, []);

useEffect(() => {

return () => tweenRef.current?.kill();

}, []);

return <div ref={boxRef} onClick={animateBox}>Click</div>;

}


## Timeline Management

### Timeline Ref Pattern

function TimelineComponent() {

const container = useRef(null);

const tl = useRef(null);

useGSAP(() => {

tl.current = gsap.timeline({ paused: true })

.to('.box', { x: 200 })

.to('.box', { y: 100 })

.to('.box', { rotation: 360 });

}, { scope: container });

const play = () => tl.current?.play();

const reverse = () => tl.current?.reverse();

const restart = () => tl.current?.restart();

return (

<div ref={container}>

<div className="box">Animated</div>

<button onClick={play}>Play</button>

<button onClick={reverse}>Reverse</button>

<button onClick={restart}>Restart</button>

</div>

);

}


### Controlled Timeline

function ControlledAnimation({ progress }) {

const container = useRef(null);

const tl = useRef(null);

useGSAP(() => {

tl.current = gsap.timeline({ paused: true })

.to('.element', { x: 500 })

.to('.element', { y: 200 });

}, { scope: container });

// Update timeline progress when prop changes

useEffect(() => {

if (tl.current) {

tl.current.progress(progress);

}

}, [progress]);

return (

<div ref={container}>

<div className="element">Controlled</div>

</div>

);

}


## ScrollTrigger in React

### Basic ScrollTrigger

import { useGSAP } from '@gsap/react';

import gsap from 'gsap';

import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

function ScrollComponent() {

const container = useRef(null);

useGSAP(() => {

gsap.from('.section', {

opacity: 0,

y: 100,

scrollTrigger: {

trigger: '.section',

start: 'top 80%',

toggleActions: 'play none none none'

}

});

}, { scope: container });

return (

<div ref={container}>

<div className="section">Scroll to reveal</div>

</div>

);

}


### ScrollTrigger Cleanup

function ScrollComponent() {

const container = useRef(null);

useGSAP(() => {

const triggers = [];

gsap.utils.toArray('.item').forEach(item => {

const trigger = ScrollTrigger.create({

trigger: item,

start: 'top 80%',

onEnter: () => gsap.to(item, { opacity: 1 })

});

triggers.push(trigger);

});

// Return cleanup function

return () => triggers.forEach(t => t.kill());

}, { scope: container });

}


## Custom Hooks

### useAnimation Hook

function useAnimation(animation, deps = []) {

const elementRef = useRef(null);

const tweenRef = useRef(null);

useGSAP(() => {

if (elementRef.current) {

tweenRef.current = animation(elementRef.current);

}

return () => tweenRef.current?.kill();

}, { dependencies: deps });

return elementRef;

}

// Usage

function Component() {

const boxRef = useAnimation((el) =>

gsap.from(el, { opacity: 0, y: 50, duration: 0.5 })

);

return <div ref={boxRef}>Animated</div>;

}


### useFadeIn Hook

function useFadeIn(options = {}) {

const { duration = 0.5, delay = 0, y = 30 } = options;

const ref = useRef(null);

useGSAP(() => {

gsap.from(ref.current, {

opacity: 0,

y,

duration,

delay,

ease: 'power2.out'

});

});

return ref;

}

// Usage

function Card() {

const cardRef = useFadeIn({ delay: 0.2 });

return <div ref={cardRef}>Card content</div>;

}


### useHoverAnimation Hook

function useHoverAnimation(enterAnimation, leaveAnimation) {

const ref = useRef(null);

const { contextSafe } = useGSAP({ scope: ref });

const onEnter = contextSafe(() => enterAnimation(ref.current));

const onLeave = contextSafe(() => leaveAnimation(ref.current));

return { ref, onMouseEnter: onEnter, onMouseLeave: onLeave };

}

// Usage

function Button() {

const hoverProps = useHoverAnimation(

(el) => gsap.to(el, { scale: 1.05, duration: 0.2 }),

(el) => gsap.to(el, { scale: 1, duration: 0.2 })

);

return <button {...hoverProps}>Hover me</button>;

}


## Temporal Collapse Patterns

### Animated Countdown Digit

function CountdownDigit({ value, label }) {

const digitRef = useRef(null);

const prevValue = useRef(value);

useGSAP(() => {

if (prevValue.current !== value) {

gsap.timeline()

.to(digitRef.current, {

rotationX: -90,

opacity: 0,

duration: 0.25,

ease: 'power2.in'

})

.call(() => {

digitRef.current.textContent = value;

prevValue.current = value;

})

.fromTo(digitRef.current,

{ rotationX: 90, opacity: 0 },

{ rotationX: 0, opacity: 1, duration: 0.25, ease: 'power2.out' }

);

}

}, { dependencies: [value] });

return (

<div className="digit-container">

<span ref={digitRef} className="digit">{value}</span>

<span className="label">{label}</span>

</div>

);

}


### Cosmic Pulse Effect

function CosmicPulse({ children, color = '#00F5FF' }) {

const containerRef = useRef(null);

useGSAP(() => {

gsap.to(containerRef.current, {

boxShadow: 0 0 30px ${color}, 0 0 60px ${color},

duration: 1,

repeat: -1,

yoyo: true,

ease: 'sine.inOut'

});

}, { scope: containerRef });

return <div ref={containerRef}>{children}</div>;

}


## Performance Tips

// 1. Use will-change for heavy animations

gsap.set('.animated', { willChange: 'transform' });

// 2. Batch similar animations

useGSAP(() => {

gsap.to('.item', { opacity: 1, stagger: 0.1 }); // Single tween

// Not: items.forEach(item => gsap.to(item, ...)) // Multiple tweens

});

// 3. Use refs over selectors for frequently animated elements

const boxRef = useRef(null);

gsap.to(boxRef.current, { x: 100 }); // Faster

// 4. Kill animations on rapid state changes

const tweenRef = useRef(null);

useEffect(() => {

tweenRef.current?.kill();

tweenRef.current = gsap.to(...);

}, [dependency]);

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card