I am trying to make a hamburger dropdown with animations on each of the menu options. Unfortunately, I have a problem with the exit animation.
I don't know how should I implement it, but what is happening is that my main navigation component - Navbar2 - has a few children of class MenuOption. I am setting a menuOpened property with true/false depending on whether the user opened the hamburger menu.
When the user opens the dropdown, MenuOption's animation runs well (open variant).
Now, the issue is when the user closes it - I guess what is happening is the MenuOption component re-renders, completely omitting the exit animation (closed variant).
How can I make the MenuOption components run the exit animation without re-rendering those components?
Here is the code:
const Navbar2 = () => {
const [menuVisible, setMenuVisible] = useState(false);
const variantsMenuDropdown = {
closed: {
height: ["150px", "150px", "0px"],
},
open: {
height: ["0px", "150px"],
},
};
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}
transition={{ duration: 0.30 }}
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 file
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 = {
closed: {
opacity: 0,
x: [-3],
},
open: {
x: [-3, -3, 5],
opacity: 1
}
}
return (
<motion.div className="flex flex-col" onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
<motion.div
variants={variantsText}
initial="preMount"
animate={menuOpened ? "open" : "closed"}
transition={{ duration: 0.50, delay: delayAnimation }}
>
<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;
1 Answer 1
I've fixed this - the issue wasn't re-rendering but much more trivial.
Here, the "closed" animation only had one keyframe (so it automatically jumped to that time frame of x: -3 wihout slowly moving there. Hope it helps someone :)
Cheers!
const variantsText = {
closed: {
opacity: 0,
x: [-3], // <== change this to x:[5,-3]
},
open: {
x: [-3, -3, 5],
opacity: 1
}
}