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

如何用Framer Motion检测无限滚动中可视区域内的Div并更新索引?

解决Framer Motion无限滚动与Dot组件状态同步问题

核心思路

要实现滚动元素与Dot组件的状态同步,关键是检测元素进入视口的时机,并关联对应元素的索引,更新activeIndex状态。以下是两种可行方案:


方案1:利用Framer Motion的whileInView钩子

Framer Motion内置了whileInView属性,可以直接监听元素是否进入视口,结合viewport配置精准控制触发条件:

  1. 为每个滚动的div添加whileInView和viewport属性,同时绑定元素索引:
{items.map((item, index) => (
  <motion.div
    key={index}
    className="scroll-item"
    // 配置视口检测范围,元素中心进入视口时触发
    viewport={{ once: false, margin: "-50%" }}
    whileInView={() => setActiveIndex(index)}
    // 你的滚动动画配置
    animate={{ y: [0, -window.innerHeight] }}
    transition={{ repeat: Infinity, duration: 3, ease: "linear" }}
  >
    {item.content}
  </motion.div>
))}
  • viewport.margin: "-50%":让元素中心进入视口时触发回调,避免频繁切换
  • once: false:无限滚动场景下需要重复触发检测
  1. 同步Dot组件:
    确保Dot组件接收activeIndex,并根据索引匹配激活样式:
<div className="dots-container">
  {items.map((_, index) => (
    <button
      key={index}
      className={`dot ${index === activeIndex ? "active" : ""}`}
    />
  ))}
</div>

方案2:使用Intersection Observer API

如果需要更自定义的视口检测逻辑,原生的Intersection Observer是更灵活的选择:

  1. 在组件挂载时创建Observer实例,监听每个滚动元素:
import { useEffect, useRef, useState } from "react";

function ScrollWithDots() {
  const [activeIndex, setActiveIndex] = useState(0);
  const itemRefs = useRef([]);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            // 获取元素索引
            const index = itemRefs.current.indexOf(entry.target);
            if (index !== -1) setActiveIndex(index);
          }
        });
      },
      {
        root: null, // 使用视口作为根
        threshold: 0.5, // 元素50%进入视口时触发
      }
    );

    // 为每个元素添加监听
    itemRefs.current.forEach((ref) => {
      if (ref) observer.observe(ref);
    });

    // 清理监听
    return () => {
      itemRefs.current.forEach((ref) => {
        if (ref) observer.unobserve(ref);
      });
    };
  }, []);

  return (
    <>
      <div className="scroll-container">
        {items.map((item, index) => (
          <motion.div
            key={index}
            ref={(el) => (itemRefs.current[index] = el)}
            className="scroll-item"
            // 你的滚动动画配置
            animate={{ y: [0, -window.innerHeight] }}
            transition={{ repeat: Infinity, duration: 3, ease: "linear" }}
          >
            {item.content}
          </motion.div>
        ))}
      </div>
      <div className="dots-container">
        {items.map((_, index) => (
          <button
            key={index}
            className={`dot ${index === activeIndex ? "active" : ""}`}
          />
        ))}
      </div>
    </>
  );
}

注意事项

  • 无限滚动场景下,要确保once: false(方案1)或不主动停止Observer监听(方案2),保证元素重复进入视口时能触发状态更新
  • 调整threshold或viewport.margin可以优化触发时机,避免快速切换导致的状态闪烁
  • 如果滚动元素是循环复制的(比如为了实现无缝无限滚动),需要额外处理索引的循环映射,避免索引越界

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:44:54