Loading...
Loading...
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.
npx skill4agent add bbeierle12/skill-mcp-claude gsap-reactnpm install gsap @gsap/reactimport { 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>
);
}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>
);
}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>
);
}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>
);
}function SingleElement() {
const boxRef = useRef(null);
useGSAP(() => {
gsap.to(boxRef.current, {
x: 200,
rotation: 360,
duration: 1
});
});
return <div ref={boxRef}>Box</div>;
}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>
);
}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>
);
}// 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 });
});
}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
}, []);
}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();
}, []);
}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>
);
}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>;
}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>
);
}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>
);
}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>
);
}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 });
}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>;
}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>;
}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>;
}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>
);
}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>;
}// 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]);gsap-fundamentalsgsap-sequencinggsap-scrolltrigger