I am trying to create a mobile menu where the nav items fade in one at a time. I've dug around but can't seem to find what I'm doing wrong here. Instead of staggering each NavLink item, they all load in at the same time.
Initially, I had thought maybe I should be using 'delay' instead of 'delayChildren' but that doesn't seem to be the case.
mobileNav.tsx
'use client';
import { useState } from "react";
import {AnimatePresence, motion} from "framer-motion";
import NavLink from '@/components/nav/navLink';
const MobileNav = () => {
const [isOpen, setOpen] = useState<boolean>(false);
const handleClick = () => {
setOpen(!isOpen);
};
const menuVariants = {
closed: {
x: '100%',
},
open: {
x: 0,
transition: {
type: 'tween',
duration: 0.3,
},
},
};
const navLinksVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
},
},
exit: {
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
},
},
};
return(
<>
<AnimatePresence>
{isOpen &&
<motion.div className="z-10 w-screen h-screen bg-gray-500 absolute flex flex-col justify-center items-center"
variants={menuVariants}
initial="closed"
animate="open">
<motion.ul
variants={navLinksVariants}
initial="hidden"
animate="visible"
exit="exit">
<NavLink link="#projects">Projects</NavLink>
<NavLink link="#about">About</NavLink>
<NavLink link="#contact">Contact</NavLink>
</motion.ul>
</motion.div>
}
</AnimatePresence>
<button onClick={handleClick} className="p-4 flex flex-col justify-center items-center absolute z-20 lg:hidden">
{!isOpen &&
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-10">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
}
{isOpen &&
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-10">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
}
</button>
</>
)
}
export default MobileNav;
navLink.tsx
'use client';
import { motion } from 'framer-motion';
interface NavLinkProps {
link: string;
children: string;
}
const NavLink: React.FC<NavLinkProps> = ({link, children}) => {
const linkItemVariants = {
hidden: { opacity: 0, y: '50%' },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: "easeOut" // Add ease-out easing function
},
},
exit: {
opacity: 0,
y: '80%',
transition: {
duration: 0.3,
ease: "easeOut" // Add ease-out easing function
}
},
};
return (
<motion.li className="lg:mx-5 sm:mb-5 text-5xl lg:text-lg"
variants={linkItemVariants}
initial="hidden"
animate="visible"
exit="exit">
<a href={link} aria-label={children}>
{children}
</a>
</motion.li>
);
};
export default NavLink;
It doesn't have anything to do with using a custom component. Remove the initial
, animate
, and exit
props from your <NavLink />
component.
In the Framer Motion Overview docs, under Propagation:
If a
motion
component has children, changes in variant will flow down through the component hierarchy until a child component defines its ownanimate
property.
Adding initial
, animate
, and exit
to children motion elements will override any variant changes from parent motion elements.
An important thing to note is that you only get access to the fancy orchestration transitions (when
, delayChildren
, staggerChildren
) when using variants. You can read more about orchestration here.
Here is a working example.