You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Motion级联动画:汉堡菜单选项退出动画失效问题求助

React Motion级联动画:汉堡菜单选项退出动画失效问题求助

看起来你遇到的问题是汉堡菜单关闭时,子选项的退出动画被父容器的高度动画直接“截断”了——父容器在子元素还没来得及播放完退出动画时,就把高度缩成0,把MenuOption组件给裁掉了,所以完全看不到退出效果。我来帮你梳理问题原因,再给出具体的解决办法:

问题核心原因

  1. 父容器动画时序冲突:你的父容器variantsMenuDropdownclosed动画是height: ["150px", "150px", "0px"],配合全局0.3s的过渡时长,这意味着父容器几乎瞬间就会从150px缩到0,而子元素MenuOption的退出动画时长是0.5s,完全赶不上父容器的收缩速度,直接被overflow:hidden隐藏。
  2. 父子动画未同步:MenuOption的动画状态切换和父容器的高度动画没有时序关联,两者各自独立运行,没有形成连贯的关闭流程。

分步解决方案

1. 调整父容器动画时序,给子元素留足退出时间

修改父容器的动画变体,让父容器在关闭时先保持高度,等子元素退出动画完成后再收缩

const variantsMenuDropdown = {
  closed: {
    height: ["150px", "150px", "0px"],
    // 给高度过渡加延迟,匹配子元素退出动画的时长
    transition: {
      height: {
        delay: 0.5,
        duration: 0.3
      }
    }
  },
  open: {
    height: ["0px", "150px"],
    transition: {
      height: {
        duration: 0.3
      }
    }
  },
};

同时移除父容器上全局的transition={{ duration: 0.30 }},把过渡配置放到变体里,让开/关动画可以分别控制时序。

2. 完善MenuOption的动画变体,确保退出动画完整展示

给MenuOption的动画变体补充初始状态,明确退出动画的参数:

const variantsText = {
  // 初始挂载状态,避免页面加载时的闪烁
  preMount: {
    opacity: 0,
    x: -3
  },
  closed: {
    opacity: 0,
    x: -3,
    transition: {
      duration: 0.5,
      delay: delayAnimation
    }
  },
  open: {
    opacity: 1,
    x: 5,
    transition: {
      duration: 0.5,
      delay: delayAnimation
    }
  }
};

然后给MenuOption的motion元素加上initial="preMount",确保初始渲染的状态正确:

<motion.div
  initial="preMount"
  animate={menuOpened ? "open" : "closed"}
  variants={variantsText}
>
  {/* 原有Link和下划线内容 */}
</motion.div>

3. 完整修改后的代码示例

Navbar2组件

const Navbar2 = () => {
  const [menuVisible, setMenuVisible] = useState(false);

  const variantsMenuDropdown = {
    closed: {
      height: ["150px", "150px", "0px"],
      transition: {
        height: {
          delay: 0.5, // 匹配子元素退出动画时长
          duration: 0.3
        }
      }
    },
    open: {
      height: ["0px", "150px"],
      transition: {
        height: {
          duration: 0.3
        }
      }
    },
  };

  return (
    <motion.div className="fixed z-50 w-full bg-white overflow-hidden" >
      <nav className="grid grid-cols-3 place-items-center pl-2 pr-2 " >
        <div onClick={() => { setMenuVisible(!menuVisible); }} >
          <HamburgerIcon className="justify-self-start" menuVisible={menuVisible} />
        </div>
        {/* 保留你原有的其他导航元素 */}
      </nav>
      <motion.div
        animate={menuVisible ? "open" : "closed"}
        variants={variantsMenuDropdown}
        className="bg-stone-50 w-full h-auto grid grid-rows-3 grid-cols-1 justify-items-start overflow-hidden pl-10 "
      >
        <div className="w-full h-auto bg-stone-50 p-1 flex items-center justify-start pl-10">
          <div onClick={() => setMenuVisible(!menuVisible)}>
            <MenuOption linkName="Shop" menuOpened={menuVisible} delayAnimation={0.03} />
          </div>
        </div>
        <div className="w-full h-auto bg-stone-50 p-1 flex items-center justify-start pl-10">
          <div onClick={() => setMenuVisible(!menuVisible)}>
            <MenuOption linkName="About" menuOpened={menuVisible} delayAnimation={0.06} />
          </div>
        </div>
        <div className="w-full h-auto bg-stone-50 p-1 flex items-center justify-start pl-10">
          <div onClick={() => setMenuVisible(!menuVisible)}>
            <MenuOption linkName="Contact" menuOpened={menuVisible} delayAnimation={0.09} />
          </div>
        </div>
      </motion.div>
    </motion.div>
  );
};

export default Navbar2;

MenuOption组件

import { useState } from "react";
import { motion } from "motion/react";
import { Link } from "react-router-dom";

const MenuOption = ({ linkName, menuOpened, delayAnimation }) => {
  const [hovered, setHovered] = useState(false);

  const variantsText = {
    preMount: {
      opacity: 0,
      x: -3
    },
    closed: {
      opacity: 0,
      x: -3,
      transition: {
        duration: 0.5,
        delay: delayAnimation
      }
    },
    open: {
      opacity: 1,
      x: 5,
      transition: {
        duration: 0.5,
        delay: delayAnimation
      }
    }
  };

  return (
    <motion.div 
      className="flex flex-col"
      onHoverStart={() => setHovered(true)}
      onHoverEnd={() => setHovered(false)}
    >
      <motion.div
        initial="preMount"
        animate={menuOpened ? "open" : "closed"}
        variants={variantsText}
      >
        <Link to={`/${linkName}`} className="relative text-black flex items-center group" >
          {linkName}
        </Link>
        <div className={`border-t-[1px] border-sky-600 rounded-xl transition-all duration-150 ease-in-out ${hovered ? 'w-full' : 'w-0'}`}></div>
      </motion.div>
    </motion.div>
  )
};

export default MenuOption;

额外提示

  • 你可以根据自己的需求调整delayduration数值,让开/关动画的节奏更自然。
  • 如果父容器的高度不是固定的150px,建议用scaleY动画替代固定高度的硬编码,或者配合motion的layout动画实现自适应高度的过渡,避免不同屏幕尺寸下的适配问题。

火山引擎 最新活动