React Motion级联动画:汉堡菜单选项退出动画失效问题求助
React Motion级联动画:汉堡菜单选项退出动画失效问题求助
看起来你遇到的问题是汉堡菜单关闭时,子选项的退出动画被父容器的高度动画直接“截断”了——父容器在子元素还没来得及播放完退出动画时,就把高度缩成0,把MenuOption组件给裁掉了,所以完全看不到退出效果。我来帮你梳理问题原因,再给出具体的解决办法:
问题核心原因
- 父容器动画时序冲突:你的父容器
variantsMenuDropdown的closed动画是height: ["150px", "150px", "0px"],配合全局0.3s的过渡时长,这意味着父容器几乎瞬间就会从150px缩到0,而子元素MenuOption的退出动画时长是0.5s,完全赶不上父容器的收缩速度,直接被overflow:hidden隐藏。 - 父子动画未同步: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;
额外提示
- 你可以根据自己的需求调整
delay和duration数值,让开/关动画的节奏更自然。 - 如果父容器的高度不是固定的150px,建议用
scaleY动画替代固定高度的硬编码,或者配合motion的layout动画实现自适应高度的过渡,避免不同屏幕尺寸下的适配问题。




