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

Framer Motion 滚动触发与悬停动画结合后状态异常回退问题

问题原因

  1. Framer Motion的whileHover属性存在默认回退逻辑:未显式指定悬停结束后的回退目标时,whileHover会默认回退到组件挂载时声明的initial状态。你在组件上配置了initial="hidden",因此每次悬停结束都会直接跳回隐藏的初始状态,不会识别后续通过动画控制器修改的show状态。
  2. 自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 18:45:05