Gsap React

Bbeierle12/Skill-MCP-Claude/skills/gsap-react

by Bbeierle12c35d6c509b8f0331697f19ef45b651f274deba60No license8 starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated today

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.

AI-generated overview

React-specific patterns for integrating GSAP animations using the useGSAP hook, refs, cleanup, and context.

What it does
This skill provides React-specific guidance for using GSAP animations, centered on the useGSAP hook and gsap.context. It covers ref patterns, automatic and manual cleanup, event handlers with contextSafe, timeline management, ScrollTrigger usage, and custom animation hooks. It produces code patterns and examples rather than files or scripts.
When to use it
Use it when implementing GSAP animations inside React components, handling component lifecycle and cleanup, or building reusable animation hooks. It is also relevant when wiring ScrollTrigger or event-driven animations into React.
Requirements
Requires the gsap and @gsap/react npm packages in a React project; instructions only, with no bundled scripts.

GSAP React Integration

React-specific patterns for GSAP animations.

Quick Start

bash
npm install gsap @gsap/react
tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
// useGSAP automatically cleans up animations on unmountfunction Component() {  useGSAP(() => {    // This timeline is automatically killed on unmount    gsap.timeline()      .to('.a', { x: 100 })      .to('.b', { x: 100 });  });}

Manual Context (Without useGSAP)

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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

tsx
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;}
// Usagefunction Component() {  const boxRef = useAnimation((el) =>    gsap.from(el, { opacity: 0, y: 50, duration: 0.5 })  );
  return <div ref={boxRef}>Animated</div>;}

useFadeIn Hook

tsx
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;}
// Usagefunction Card() {  const cardRef = useFadeIn({ delay: 0.2 });  return <div ref={cardRef}>Card content</div>;}

useHoverAnimation Hook

tsx
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 };}
// Usagefunction 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

tsx
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

tsx
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

tsx
// 1. Use will-change for heavy animationsgsap.set('.animated', { willChange: 'transform' });
// 2. Batch similar animationsuseGSAP(() => {  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 elementsconst boxRef = useRef(null);gsap.to(boxRef.current, { x: 100 }); // Faster
// 4. Kill animations on rapid state changesconst 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

Source and attribution

Source:Bbeierle12/Skill-MCP-Claudeinskills/gsap-reactat commitc35d6c5

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal