Im am trying to create a side menu that pops in and out when the menu/close button is pressed using framer motion. When the menu button is first clicked the menu will slide out from the left hand side of the screen and the button changes to closed. When now the close button is pressed the menu should slide back out however, it just disappears with no animation
I tried to wrap the motion.div in AnimaiatePresence so that i can then add a exit animation as shown below:
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
const menuItems = ["Home", "Projects", "Blog", "Recipes"];
const menuVariant = {
hidden: { opacity: 0, x: "-100vw" },
show: {
opacity: 1,
x: 0,
transition: { style: "tween", duration: 0.75 },
},
exit: {
x: "-100vw",
transition: { ease: "easeInOut" },
},
};
const SideMenu = () => {
return (
<AnimatePresence>
<motion.div
className="flex flex-col px-10 bg-orange-400 max-h-full w-2/5 rounded-r-full round"
variants={menuVariant}
initial="hidden"
animate="show"
exit="exit"
>
{menuItems.map((item) => (
<button className="my-3 text-4xl text-left">{item}</button>
))}
</motion.div>
</AnimatePresence>
);
};
export default SideMenu;
The above component is utilised in the following home page code
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import SideMenu from "../components/sideMenu";
const buttonVariant = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { duration: 1, delay: 0.5 } },
};
export default function Home() {
const [menuState, setMenuState] = useState(false);
return (
<>
<motion.button
className={`m-10 p-4 border-2 rounded-3xl ${
menuState ? "border-red-500" : "border-sky-500"
}`}
variants={buttonVariant}
initial="hidden"
animate="show"
onClick={() => {
setMenuState(!menuState);
}}
>
{menuState ? "Close" : "Menu"}
</motion.button>
{menuState ? <SideMenu /> : <></>}
</>
);
}
AnimatePresence
only works when it's direct children are being removed from the React tree. You need to move your conditional rendering logic to the <SideMenu />
:
const SideMenu = () => {
const [menuState, setMenuState] = useState(false);
return (
<>
{/* Moved trigger button into the SideMenu */}
<motion.button
className={`m-10 p-4 border-2 rounded-3xl ${
menuState ? "border-red-500" : "border-sky-500"
}`}
variants={buttonVariant}
initial="hidden"
animate="show"
onClick={() => {
setMenuState(!menuState);
}}
>
{menuState ? "Close" : "Menu"}
</motion.button>
{/* Conditionally show menu if menuState is 'true' */}
<AnimatePresence>
{menuState && (
<motion.div
className="flex flex-col px-10 bg-orange-400 max-h-full w-2/5 rounded-r-full round"
variants={menuVariant}
initial="hidden"
animate="show"
exit="exit"
>
{menuItems.map((item) => (
<button key={item} className="my-3 text-4xl text-left">
{item}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</>
);
};
Here's a working example if you need more help.