GSAP React Integration
React-specific patterns for GSAP animations.
Quick Start npm 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 (
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 (
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 (
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 (
Ref Patterns Single Element Ref function SingleElement() { const boxRef = useRef(null);
useGSAP(() => { gsap.to(boxRef.current, { x: 200, rotation: 360, duration: 1 }); });
return
Multiple Element Refs function MultipleElements() { const itemsRef = useRef([]);
useGSAP(() => { gsap.from(itemsRef.current, { opacity: 0, y: 30, stagger: 0.1 }); });
return (
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 (
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 (
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
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 (
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 (
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 (
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
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
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 ; }
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 (
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
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]);
Reference See gsap-fundamentals for animation basics See gsap-sequencing for timeline composition See gsap-scrolltrigger for scroll-based animations