Gsap React

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

作者 Bbeierle12c35d6c509b8f0331697f19ef45b651f274deba60無授權條款8 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫今天更新

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 產生的概覽

在 React 中整合 GSAP 動畫的專用模式,涵蓋 useGSAP、ref、清理與情境管理。

功能
此技能提供在 React 中使用 GSAP 動畫的針對性指引,核心是 useGSAP hook 與 gsap.context。內容涵蓋 ref 用法、自動與手動清理、使用 contextSafe 的事件處理、時間軸管理、ScrollTrigger 用法以及自訂動畫 hook。產出的是程式碼模式與範例,而非檔案或指令碼。
適用情境
適用於在 React 元件中實作 GSAP 動畫、處理元件生命週期與清理,或建立可重複使用的動畫 hook。也適合將 ScrollTrigger 或事件驅動動畫接入 React 時使用。
執行需求
需要在 React 專案中安裝 gsap 與 @gsap/react npm 套件;僅為說明性內容,不附帶指令碼。

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

來源與署名

來源:Bbeierle12/Skill-MCP-Claude位於skills/gsap-react提交c35d6c5

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架