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
相关产品推荐
相关产品推荐

