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

如何使用Framer Motion实现滚动经过元素后的类名添加及动画状态双向切换?

如何使用Framer Motion实现滚动经过元素后的类名添加及动画状态双向切换?

嘿,完全懂你要的效果!之前用GSAP的ScrollTrigger实现了元素进视口显示、子元素滚动动画,滚过去后保持可见,往上滚时反向动画、滚回视口外就隐身的逻辑对吧?换成Framer Motion来做的话,用它的滚动钩子和动画控制器就能轻松复刻,而且双向状态切换的逻辑也能精准控制~

核心思路

其实就是靠Framer Motion的几个核心钩子,跟踪三个关键状态来控制逻辑:

  • 元素是否在当前视口中
  • 滚动的方向(向上/向下)
  • 动画的正向/反向播放状态

然后把这三个状态结合起来,就能对应实现类名切换、元素可见性控制和子元素的动画双向触发。

完整代码实现(React + Framer Motion)

我给代码加了详细注释,一眼就能看明白每部分的作用:

import { motion, useScroll, useInView, useAnimation } from "framer-motion";
import { useEffect, useRef } from "react";

export default function ScrollAnimatedSection() {
  // 创建动画控制器,用来手动触发正向/反向动画
  const sectionControls = useAnimation();
  const childControls = useAnimation();
  // 绑定目标section的DOM引用,用来监听位置和操作类名
  const sectionRef = useRef(null);
  // 监听元素是否在视口中:once设为false才能重复触发,rootMargin调整触发缓冲(上下各留10%的范围)
  const isSectionInView = useInView(sectionRef, { once: false, rootMargin: "-10% 0 -10% 0" });
  // 获取当前滚动方向(up/down)
  const { direction } = useScroll();

  // 监听视口状态和滚动方向,控制section的可见性与类名
  useEffect(() => {
    if (isSectionInView) {
      // 元素进入视口:播放正向显示动画,添加激活类
      sectionControls.start({ opacity: 1, transition: { duration: 0.5 } });
      sectionRef.current.classList.add("active");
      // 同时触发子元素的滚动入场动画
      childControls.start({
        x: 0,
        opacity: 1,
        transition: { duration: 0.6, staggerChildren: 0.2 }
      });
    } else {
      // 元素离开视口时,根据滚动方向分逻辑处理
      if (direction === "down") {
        // 向下滚动离开视口:保持激活类,元素维持可见(对应你要的"滚过去后不消失")
        sectionRef.current.classList.add("active");
      } else if (direction === "up") {
        // 向上滚动离开视口:触发反向动画,移除激活类,元素隐藏
        sectionControls.start({ opacity: 0, transition: { duration: 0.5 } });
        sectionRef.current.classList.remove("active");
        // 子元素触发反向退场动画
        childControls.start({
          x: -50,
          opacity: 0,
          transition: { duration: 0.6, staggerChildren: 0.2 }
        });
      }
    }
  }, [isSectionInView, direction, sectionControls, childControls]);

  return (
    <motion.section
      ref={sectionRef}
      className="scroll-section"
      initial={{ opacity: 0 }}
      animate={sectionControls}
    >
      {/* 子元素示例,用staggerChildren实现依次入场/退场的效果 */}
      <motion.div className="child-item" initial={{ x: -50, opacity: 0 }} animate={childControls}>
        子元素1
      </motion.div>
      <motion.div className="child-item" initial={{ x: -50, opacity: 0 }} animate={childControls}>
        子元素2
      </motion.div>
      <motion.div className="child-item" initial={{ x: -50, opacity: 0 }} animate={childControls}>
        子元素3
      </motion.div>
    </motion.section>
  );
}

配套基础CSS样式

给元素加些基础样式,让动画效果更直观:

.scroll-section {
  min-height: 600px;
  margin: 120px 0;
  padding: 20px;
  border: 1px solid #eee;
}

.child-item {
  margin: 24px 0;
  padding: 16px;
  background-color: #f5f5f5;
  border-radius: 8px;
}

关键逻辑对应(和你之前的GSAP实现对比)

  • 之前GSAP的toggleClass:这里通过classList.add/remove结合isInView和滚动方向,实现更精准的类名切换时机
  • onLeave(向下滚动离开视口):对应direction === "down"的逻辑,保持active类,元素维持可见
  • onEnterBack(向上滚动回到视口上方):对应direction === "up"且元素不在视口中的逻辑,触发反向动画、移除类名、隐藏元素

这样调整后,完全能实现你要的双向状态切换效果,而且Framer Motion的钩子用起来很灵活,还能根据需求微调触发时机和动画参数~

备注:内容来源于stack exchange,提问作者TejaML

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:44:48