I am currently working on building a portfolio website utilizing React and Tailwind. I am attempting to implement a mobile menu button and dropdown menu that animates using CSS transitions. I am conditionally applying an "open" class to the component using the classnames library based on the state set in the parent element. Unfortunately, whenever the state in the parent element is changed, i.e. the button is clicked and the state is updated, the CSS updates instantaneously instead of transitioning like expected. The transforms are applied immediately instead of over time.
I am able to enable/disable CSS rules to force the transition to animate in the inspector. If I remove the state hook from the parent element and utilize individual state in each children component, the transitions will also play out properly individually, but I cannot figure out how to make both transitions play simultaneously.
I've been able to track it down and I assume that it is because the parent component is rendering again and stopping the transition in it's tracks, but I cannot figure out how to circumvent the update from the resulting state change. I have tried memoize the parent component, but it still fails to render properly.
Any help in figuring out how to get the transitions to play properly would be greatly appreciated.
Parent element:
...
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const handleMobileMenuClick = () => {
setMobileNavOpen(!mobileNavOpen);
};
...
return (
<nav>
<CenteredContainer>
{/* Padding for absolute elements don't inherit. If padding needs to be changed on center container, also change it here */}
<div className="absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-6 pt-4 mx-auto md:pt-2 md:px-0">
<Logo />
<NavLinks />
<MobileMenuButton open={mobileNavOpen} />
</div>
</CenteredContainer>
<MobileNavMenu open={mobileNavOpen} />
</nav>
);
Child elements
const MobileMenuButton = ({ open }) => {
const tailwindClasses = "flex md:hidden mt-2 ";
var animateMobileMenu = classNames(`${tailwindClasses} nav-icon`, {
open: open,
});
return (
<div onClick={handleMobileMenuClick} className={animateMobileMenu}>
<span></span>
<span></span>
<span></span>
</div>
);
};
const MobileNavMenu = ({ open }) => {
const tailwindClasses =
"h-1/2 w-5/6 bg-white right-0 left-0 top-20 mx-auto flex flex-col z-20 ";
const displayMobileNavLinks = classNames(
`${tailwindClasses} mobile-nav-menu`,
{
open: open,
}
);
return (
<div className={displayMobileNavLinks}>
<ul>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
</ul>
</div>
);
};
CSS:
.nav-icon {
width: 40px;
height: 40px;
position: relative;
cursor: pointer;
}
.nav-icon span {
position: absolute;
height: 15%;
width: 100%;
background-color: #6ee7b7;
border-radius: 9px;
opacity: 1;
left: 0;
transform: rotate(0deg);
transition: all 0.5s ease;
}
.nav-icon span:nth-child(1) {
top: 0%;
transform-origin: left center;
}
.nav-icon span:nth-child(2) {
top: 35%;
transform-origin: left center;
}
.nav-icon span:nth-child(3) {
top: 70%;
transform-origin: left center;
}
.nav-icon.open span:nth-child(1) {
transform: rotate(45deg);
left: 8px;
}
.nav-icon.open span:nth-child(2) {
width: 0%;
opacity: 0;
}
.nav-icon.open span:nth-child(3) {
transform: rotate(-45deg);
left: 8px;
}