Framer Motion 滚动触发与悬停动画结合后状态异常回退问题
问题原因
- Framer Motion的
whileHover属性存在默认回退逻辑:未显式指定悬停结束后的回退目标时,whileHover会默认回退到组件挂载时声明的initial状态。你在组件上配置了initial="hidden",因此每次悬停结束都会直接跳回隐藏的初始状态,不会识别后续通过动画控制器修改的show状态。 - 自定义Hook的动画控制逻辑执行时机错误:你将
controls.start的判断逻辑直接写在Hook顶层,没有用useEffect包裹,每次组件重渲染时这段逻辑都会重复执行,可能打断当前动画状态的维持,进一步加剧状态回退问题。
修复方案
第一步:修复自定义Hook逻辑
将动画控制逻辑移入useEffect,确保仅在元素可见性变化时才触发状态更新:
import { useEffect } from 'react'; import { useAnimation } from 'framer-motion'; import { useInView } from 'framer-motion/dom'; // 按需调整引入路径 export const useScroll = (threshold = .2) => { const controls = useAnimation(); const [element, view] = useInView({ threshold: threshold, triggerOnce: true }); useEffect(() => { if (view) { controls.start('show'); } else { controls.start('hidden'); } }, [view, controls]); return [element, controls]; }
第二步:修复悬停回退问题,二选一即可
方案1:显式指定悬停结束后的状态
在卡片组件上添加onHoverEnd回调,悬停结束后手动切回show状态:
const [element, controls] = useScroll(); <StyledCard onClick={openCardHandler} style={style} layoutId={content.id} ref={element} variants={popup} initial="hidden" animate={controls} whileHover="hover" onHoverEnd={() => controls.start('show')} > {/* 卡片内容 */} </StyledCard>
方案2:移除组件上的初始状态声明
去掉组件的initial="hidden"属性,将初始状态的控制权完全交给动画控制器,whileHover结束后会自动回退到controls当前的show状态:
const [element, controls] = useScroll(); <StyledCard onClick={openCardHandler} style={style} layoutId={content.id} ref={element} variants={popup} animate={controls} whileHover="hover" > {/* 卡片内容 */} </StyledCard>
采用这个方案需要同步调整Hook,在初始化时就将控制器状态设置为hidden:
const controls = useAnimation(); // 初始化后立刻设置初始状态 useEffect(() => { controls.set('hidden'); }, [controls]);
内容的提问来源于stack exchange,提问作者DeadApe
相关产品推荐
相关产品推荐

