| name | gsap-react |
| description | GSAP integration with React including useGSAP hook, ref handling, cleanup patterns, and context management. Use when implementing GSAP animations in React components, handling component lifecycle, or building reusable animation hooks. |
GSAP React Integration
React-specific patterns for GSAP animations.
Quick Start
bun install gsap @gsap/react
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
function Component() {
const containerRef = useRef(null);
useGSAP(
() => {
gsap.to(".box", { x: 200, duration: 1 });
},
{ scope: containerRef },
);
return (
<div ref={containerRef}>
<div className="box">Animated</div>
</div>
);
}
useGSAP Hook
Basic Usage
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
function AnimatedComponent() {
const container = useRef(null);
useGSAP(
() => {
gsap.from(".item", {
opacity: 0,
y: 50,
stagger: 0.1,
});
},
{ scope: container },
);
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 },
);
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
function Component() {
useGSAP(() => {
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();
}, []);
}
Scoped Context
function Component() {
const containerRef = useRef(null);
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to(".item", { opacity: 1 });
}, containerRef);
return () => ctx.revert();
}, []);
}
Event Handlers
contextSafe for Events
function InteractiveComponent() {
const container = useRef(null);
const { contextSafe } = useGSAP(
() => {
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 },
);
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 () => 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;
}
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;
}
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 };
}
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>;
}
Performance Tips
gsap.set('.animated', { willChange: 'transform' });
useGSAP(() => {
gsap.to('.item', { opacity: 1, stagger: 0.1 });
});
const boxRef = useRef(null);
gsap.to(boxRef.current, { x: 100 });
const tweenRef = useRef(null);
useEffect(() => {
tweenRef.current?.kill();
tweenRef.current = gsap.to(...);
}, [dependency]);