如何使用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
相关产品推荐
相关产品推荐

