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

Next.js中Intersection Observer仅对最后一个元素生效的解决方法

问题解决:循环渲染元素的视口可见性透明度切换失效

问题根源

你使用了单个useRef绑定所有循环生成的div,每次map迭代时,ref会被最后一个元素覆盖,导致只有最后一个div能被IntersectionObserver观察到。


解决方案1:用数组存储多个元素的Ref

通过useRef维护一个元素数组,给每个循环生成的div单独绑定ref,并批量添加观察器:

const AboutMe: NextPage = ({ data }: any) => {
  // 初始化数组Ref存储所有目标元素
  const targetsRef = useRef<HTMLDivElement[]>([]);

  useEffect(() => {
    // 创建统一的观察器实例
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          const target = entry.target as HTMLElement;
          // 根据可见性切换透明度
          target.style.opacity = entry.isIntersecting ? "1" : "0";
        });
      },
      { threshold: 0.5 }
    );

    // 给每个元素添加观察
    targetsRef.current.forEach((el) => {
      if (el) observer.observe(el);
    });

    // 组件卸载时清理观察器,避免内存泄漏
    return () => {
      targetsRef.current.forEach((el) => {
        if (el) observer.unobserve(el);
      });
    };
  }, [data]); // 依赖data,确保数据更新时重新绑定观察

  return (
    <main>
      <section className="container px-5 py-24 mx-auto space-y-20">
        {data.results?.map((item: any, index: number) => {
          return (
            <div
              className="opacity-0 transition-all duration-500"
              key={item.id}
              // 通过函数式ref给数组对应位置赋值
              ref={(el) => {
                targetsRef.current[index] = el as HTMLDivElement;
              }}
            >
              <ol>
                <li>
                  {/* 增加空值判断避免报错 */}
                  {item.properties.content.rich_text[0]?.plain_text || ''}
                </li>
              </ol>
            </div>
          );
        })}
      </section>
    </main>
  );
};

解决方案2:封装子组件单独管理逻辑

将每个需要动画的元素拆分为独立子组件,每个子组件单独使用useRef和观察器,更符合React组件化思想:

// 子组件:单独处理单个元素的动画逻辑
const AnimatedItem: React.FC<{ content: string }> = ({ content }) => {
  const targetRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!targetRef.current) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        const target = entry.target as HTMLElement;
        target.style.opacity = entry.isIntersecting ? "1" : "0";
      },
      { threshold: 0.5 }
    );

    observer.observe(targetRef.current);

    // 清理观察器
    return () => observer.unobserve(targetRef.current);
  }, []);

  return (
    <div
      className="opacity-0 transition-all duration-500"
      ref={targetRef}
    >
      <ol>
        <li>{content}</li>
      </ol>
    </div>
  );
};

// 父组件:循环渲染子组件
const AboutMe: NextPage = ({ data }: any) => {
  return (
    <main>
      <section className="container px-5 py-24 mx-auto space-y-20">
        {data.results?.map((item: any) => (
          <AnimatedItem
            key={item.id}
            content={item.properties.content.rich_text[0]?.plain_text || ''}
          />
        ))}
      </section>
    </main>
  );
};

方案对比

  • 数组Ref方案:适合简单场景,无需额外拆分子组件,代码集中性强
  • 子组件方案:逻辑模块化,维护更方便,适合后续扩展复杂动画逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 19:22:30