mcpField
G
gsap-react

gsap-react

github.com/bbeierle12设计与 UI9725 次安装1 天前更新
代码仓库

关于 gsap-react

gsap-react 是一项在 React 组件中使用 GSAP 动画模式的技能,涵盖 @gsap/react Hook、引用、清理、时间轴、ScrollTrigger 和可复用动画 Hook。

主要功能

  1. 01useGSAP Hook 模式
  2. 02单元素与列表引用
  3. 03作用域上下文和自动清理
  4. 04事件安全的动画处理
  5. 05时间轴与 ScrollTrigger 模式
  6. 06可复用动画 Hook

适用场景

  1. 01在组件挂载或更新时添加动画
  2. 02管理由组件状态控制的时间轴
  3. 03响应事件触发元素动画
  4. 04构建可复用的淡入或悬停 Hook

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill gsap-react

安装后在 Claude Code 中输入/gsap-react确认已加载。

附带文件_meta.json

详细介绍

GSAP React 集成

适用于 GSAP 动画的 React 专属模式。

快速开始

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 (
    <div ref={containerRef}>
      <div className="box">Animated</div>
    </div>
  );
}

useGSAP Hook

基本用法
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>
  );
}
返回 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 (
    <div ref={container}>
      <div className="box" onClick={handleClick}>Click me</div>
    </div>
  );
}

Ref 模式

单个元素 Ref
function SingleElement() {
  const boxRef = useRef(null);

  useGSAP(() => {
    gsap.to(boxRef.current, {
      x: 200,
      rotation: 360,
      duration: 1
    });
  });

  return <div ref={boxRef}>Box</div>;
}
多个元素 Ref
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>
  );
}
动态 Ref
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 和清理

自动清理
// 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 });
  });
}
手动 Context(不使用 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
  }, []);
}
作用域 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();
  }, []);
}

事件处理器

为事件使用 contextSafe
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 替代方案
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>;
}

时间线管理

时间线 Ref 模式
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>
  );
}

React 中的 ScrollTrigger

基本 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
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 });
}

自定义 Hook

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 <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;
}

// Usage
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 };
}

// 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-fundamentals,了解动画基础知识
  • 查看 gsap-sequencing,了解时间线编排
  • 查看 gsap-scrolltrigger,了解基于滚动的动画

评论

支持 Markdown