You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中useRef结合GSAP ScrollTrigger的问题及水平滚动实现

问题解答

1. 解决单个ref仅获取最后一个元素的问题

你当前的核心问题是单个useRef无法绑定多个DOM元素——每次给<Panel>赋值ref={panelRef}时,panelRef.current会被后续元素覆盖,最终只保留最后一个面板。正确的做法是用ref数组收集所有面板:

const Home = () => {
  const panelRefs = useRef([]);
  const containerRef = useRef(null);

  // 函数式ref:将每个面板元素加入数组
  const setPanelRef = (el) => {
    if (el && !panelRefs.current.includes(el)) {
      panelRefs.current.push(el);
    }
  };

  useLayoutEffect(() => {
    const sections = panelRefs.current;
    if (!sections.length) return;

    gsap.to(sections, {
      xPercent: -100 * (sections.length - 1),
      scrollTrigger: {
        trigger: containerRef.current,
        pin: true,
        scrub: 1,
        end: `+=${sections.length * 1000}`, // 按面板数量动态计算结束位置
      },
    });

    // 组件卸载时清空ref数组
    return () => {
      panelRefs.current = [];
    };
  }, []);

  return (
    <Container ref={containerRef}>
      <Panel className="panel" ref={setPanelRef}>ONE</Panel>
      <Panel className="panel" ref={setPanelRef}>TWO</Panel>
      <Panel className="panel" ref={setPanelRef}>THREE</Panel>
    </Container>
  );
};

2. gsap.utils.toArray 的参数范围

toArray并非只能接收className,它支持多种参数类型:

  • CSS选择器(如.panel、#container)
  • NodeList(如document.querySelectorAll('.panel'))
  • DOM元素数组(如上面的panelRefs.current)
  • 单个DOM元素(会自动转为单元素数组)

比如你也可以直接用选择器获取元素,无需ref:

const sections = gsap.utils.toArray('.panel');

只要确保在useLayoutEffect执行时,DOM已经渲染完成即可。

3. 其他水平滚动实现方案

除了当前的xPercent平移方案,还有两种常用思路:

方案一:操作容器原生滚动位置

直接控制容器的scrollLeft属性,模拟原生水平滚动体验:

useLayoutEffect(() => {
  const container = containerRef.current;
  const totalScrollWidth = container.scrollWidth - container.clientWidth;
  if (!totalScrollWidth) return;

  gsap.to(container, {
    scrollLeft: totalScrollWidth,
    scrollTrigger: {
      trigger: container,
      pin: true,
      scrub: 1,
      end: `+=${totalScrollWidth}`, // 根据实际滚动宽度设置结束位置
    },
  });
}, []);

注意:容器需保持横向排列(你的现有样式已经满足),无需额外修改布局。

方案二:多面板独立触发动画(适合复杂场景)

如果需要给每个面板添加独立的进入/退出动画,可以开启ScrollTrigger的horizontal: true模式,为每个面板单独配置触发器:

useLayoutEffect(() => {
  const sections = gsap.utils.toArray('.panel');
  if (!sections.length) return;

  // 创建容器级滚动动画
  const containerScroll = ScrollTrigger.create({
    trigger: containerRef.current,
    pin: true,
    scrub: 1,
    horizontal: true,
    end: `+=${sections.length * 1000}`,
  });

  // 给每个面板添加入场动画
  sections.forEach((section) => {
    gsap.fromTo(section, {
      opacity: 0,
      x: 50,
    }, {
      opacity: 1,
      x: 0,
      scrollTrigger: {
        trigger: section,
        containerAnimation: containerScroll,
        start: 'left center',
        end: 'right center',
        scrub: 1,
      },
    });
  });

  return () => {
    containerScroll.kill();
  };
}, []);

内容的提问来源于stack exchange,提问作者chebread

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 18:52:37