如何让Framer Motion仅在元素进入视口时执行滚动动画
问题分析
你移除else分支后动画仅触发一次的原因是:第一次元素进入视口时,control执行了visible动画,元素停留在动画后的状态;当元素离开视口,isInView变为false但你未做任何操作,control仍保持visible状态;后续元素再次进入时,调用control.start("visible")不会触发新的动画——因为Framer Motion不会重复执行当前已处于激活状态的动画。
以下是两种符合需求的解决方案:
方案1:动画仅触发一次(第一次进入后永久保持状态)
适合只需要元素首次进入视口时执行一次动画,后续进出不再变化的场景。
修改后的Hook代码:
import { useAnimation, useInView } from "framer-motion" import { useEffect, useRef } from "react" export const useAnimateOnScroll = (inView) => { const parentRef = useRef(null) const control = useAnimation() // 开启once: true,isInView仅在元素首次进入视口时变为true,后续保持不变 const isInView = useInView(parentRef, { once: true, amount: inView, }) useEffect(() => { if (isInView) { control.start("visible") } // 移除else分支,不处理元素离开的逻辑 }, [isInView, control]) return { parentRef, control } }
方案2:每次进入视口都触发动画(离开时保持当前状态)
适合需要元素每次进入视口都重新播放动画,离开时不执行反向动画的场景。
修改后的Hook代码:
import { useAnimation, useInView } from "framer-motion" import { useEffect, useRef, useState } from "react" export const useAnimateOnScroll = (inView) => { const parentRef = useRef(null) const control = useAnimation() // 标记元素是否处于视口外,用于判断是否需要重置动画状态 const [isOutOfView, setIsOutOfView] = useState(false) const isInView = useInView(parentRef, { once: false, amount: inView, }) useEffect(() => { if (isInView) { if (isOutOfView) { // 先无过渡重置到初始状态,再播放动画 control.set("hidden") control.start("visible") setIsOutOfView(false) } else { // 首次进入时直接播放动画 control.start("visible") } } else { // 元素离开时仅标记状态,不执行反向动画 setIsOutOfView(true) } }, [isInView, control, isOutOfView]) return { parentRef, control } }
示例使用方式
配合Framer Motion的variants定义动画效果:
import { motion } from "framer-motion" import { useAnimateOnScroll } from "./useAnimateOnScroll" const AnimatedSection = () => { const { parentRef, control } = useAnimateOnScroll(0.5) const animationVariants = { hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { duration: 0.7 } } } return ( <motion.div ref={parentRef} variants={animationVariants} initial="hidden" animate={control} className="section-content" > 滚动到此处触发动画 </motion.div> ) }
内容的提问来源于stack exchange,提问作者first
相关产品推荐
相关产品推荐

