导航栏组件代码
import Humburger from "./ui/Humburger";
const navlins = ["HOME", "PAGES", "PRODUCTS", "ARTICLES", "CONTACT"];
const Navbar = () => {
const [isNavbarActive, setIsNavbarActive] = useState(false);
const toogleNavbar = () => setIsNavbarActive(!isNavbarActive);
return (
<>
<div className="relative bg-white h-[4.5rem] z-20">
<div className="w-full h-full flex justify-between items-center px-4">
<Image
width={120}
height={70}
src="https://cdn.prod.website-files.com/65478d390c8996a757a4faaa/65c4aacccb9a41d80d0d66f7_Stride-logo-dark.svg"
alt="nav-logo"
/>
<div className="hidden">
<ul className="flex w-full border border-green-500">
<li>Home</li>
<li>Pages</li>
<li>Product</li>
<li>Article</li>
<li>contact</li>
</ul>
</div>
<div className="flex items-center gap-4">
<div className="relative h-10 w-10 p-2">
<Image
className="z-0 inset-0"
src="https://cdn.prod.website-files.com/65478d390c8996a757a4faaa/659e5cb3e032b694713f63c0_Add%20to%20basket.svg"
width={20}
height={20}
alt="cart-icon"
/>
<div className="z-20 absolute shadow-xl top-1 -right-[2px] bg-gray-200 text-black rounded-full text-[0.6em] h-[1.1rem] w-[1.1rem] grid place-content-center font-bold">
5
</div>
</div>
<button className="border flex items-center justify-center border-slate-400 hover:focus-within:none bg-black text-white rounded-full px-4 py-1.5 hover:bg-white transition-all ease-in hover:text-black">
Login
</button>
<Humburger onClick={() => setIsNavbarActive(!isNavbarActive)} />
</div>
</div>
</div>
<AnimatePresence mode="popLayout">
{isNavbarActive && <NavbarDropdown />}
</AnimatePresence>
</>
);
};
export default Navbar;
const NavbarDropdown = () => {
const dropdownVariants = {
initial: {
opacity: 0,
height: 0,
},
enter: {
opacity: 1,
height: "14rem",
transition: {
ease: [0.83, 0, 0.17, 1],
duration: 0.5,
staggerChildren: 0.1,
staggerDirection: 1,
delayChildren: 0.2,
// Stagger the li children after the dropdown opens
},
},
exit: {
height: 0,
transition: {
ease: [0.83, 0, 0.17, 1],
staggerChildren: 0.1, // Stagger the li children on exit as well
staggerDirection: -1,
delay: 0.3,
// Reverse the direction (exit from bottom to top)
// Delay before shrinking the dropdown after the li items
},
},
};
const liVariants = {
initial: {
opacity: 0,
x: -50,
},
enter: {
opacity: 1,
x: 0,
transition: {
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
},
},
exit: {
opacity: 0,
x: -50,
transition: {
ease: [0.22, 1, 0.36, 1],
},
},
};
return (
<motion.div
variants={dropdownVariants}
initial="initial"
animate="enter"
exit="exit"
className="absolute origin-top top-[4.6rem] w-full bg-white drop-shadow-lg"
>
<motion.ul className="space-y-5 px-3">
{navlins.map((navlin, index) => (
<motion.li
custom={index}
key={index}
variants={liVariants} // Apply variants to each li item
className="text-gray-600 text-[1em] font-normal flex items-center gap-5"
>
{navlin}
{index === 0 || index === 1 ? (
<IoMdArrowDown size={24} color="gray" />
) : (
<></>
)}
</motion.li>
))}
</motion.ul>
</motion.div>
);
};
Humburger 组件代码
"use client";
import { useAnimate } from "framer-motion";
import React, { forwardRef, useState } from "react";
const Humburger = forwardRef<HTMLDivElement, { onClick: () => void }>(
({ onClick }, forwardedRef) => {
const [scope, animate] = useAnimate();
const [isHumburgOpen, setIsHumburgOpen] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const animateHumburger = async () => {
if (isAnimating) return null;
setIsAnimating(!isAnimating);
onClick();
if (!isHumburgOpen) {
animate(
"#top",
{
y: "0.45rem",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
await animate(
"#bottom",
{
y: `-${0.45}rem`,
opacity: 0,
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
animate(
"#top",
{
rotate: "136deg",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
animate(
"#middle",
{
rotate: "43deg",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
setIsHumburgOpen(!isHumburgOpen);
} else {
animate(
"#top",
{
rotate: "0deg",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
await animate(
"#middle",
{
rotate: "0deg",
},
{
ease: [0.36, 0, 0.66, -0.56],
duration: 0.5,
}
);
animate(
"#top",
{
y: "0rem",
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
await animate(
"#bottom",
{
y: 0,
opacity: 1,
},
{
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
}
);
setIsHumburgOpen(!isHumburgOpen);
}
setIsAnimating(false);
};
return (
<div ref={scope}>
<a
onClick={animateHumburger}
className="hover:focus-within:outline-none hover:focus:outline-none flex flex-col items-end justify-center h-24 gap-1 mt-50 cursor-pointer"
>
<span
id="top"
className="origin-center h-[3px] w-6 bg-black block rounded-full"
></span>
<span
id="middle"
className="h-[3px] w-6 bg-black rounded-full"
></span>
<span
id="bottom"
className="h-[3px] w-4 bg-black rounded-full"
></span>
</a>
</div>
);
}
);
export default Humburger;
函数 animateHumburger 只是将三个垂直条动画化为十字。 我正在使用framermotion来制作动画,并且正在使用useAnimate钩子,我必须将范围传递给主容器并将id传递给子元素,通过它我将在useanimatehook中使用它们来制作动画。 这个 humburger 组件完成后,我在 navbar 组件中使用了它,但出现错误。但是 ref 正在 humburger 组件本身中使用,为什么我需要按照 chatgpt 的建议传递 useRef 钩子,尽管它也没有解决问题。 无法给函数组件提供 refs 它实际上意味着什么? 即使在反应文档中,他们也已在功能组件中使用,或者我弄错了。请纠正我并帮助我解决问题??
还给我解决如何修复此错误的问题
问题在于
NavbarDropdown
没有包裹在forwardRef
中。
文档状态:
自定义组件注意:使用 popLayout 模式时,作为自定义组件的 AnimatePresence 的任何直接子组件都必须包装在 React 的forwardRef 函数中,将提供的引用转发到您希望从布局中弹出的 DOM 节点。
您正在使用自定义组件 (
NavbarDropdown
) 作为 AnimatePresence
的直接子组件,并且您还使用 popLayout
模式:
<AnimatePresence mode="popLayout">
{isNavbarActive && <NavbarDropdown />}
</AnimatePresence>
所以这个限制适用于你。
要修复,请用
NavbarDropdown
包裹 forwardRef
并将该引用应用于要“弹出”的元素。我在这里假设它是 NavbarDropdown
中的第一个元素。
const NavbarDropdown = forwardRef<HTMLDivElement, {}>((props, ref) => {
const dropdownVariants = {
initial: {
opacity: 0,
height: 0,
},
enter: {
opacity: 1,
height: "14rem",
transition: {
ease: [0.83, 0, 0.17, 1],
duration: 0.5,
staggerChildren: 0.1,
staggerDirection: 1,
delayChildren: 0.2,
// Stagger the li children after the dropdown opens
},
},
exit: {
height: 0,
transition: {
ease: [0.83, 0, 0.17, 1],
staggerChildren: 0.1, // Stagger the li children on exit as well
staggerDirection: -1,
delay: 0.3,
// Reverse the direction (exit from bottom to top)
// Delay before shrinking the dropdown after the li items
},
},
};
const liVariants = {
initial: {
opacity: 0,
x: -50,
},
enter: {
opacity: 1,
x: 0,
transition: {
ease: [0.22, 1, 0.36, 1],
duration: 0.5,
},
},
exit: {
opacity: 0,
x: -50,
transition: {
ease: [0.22, 1, 0.36, 1],
},
},
};
return (
<motion.div
ref={ref}
variants={dropdownVariants}
initial="initial"
animate="enter"
exit="exit"
className="absolute origin-top top-[4.6rem] w-full bg-white drop-shadow-lg"
>
<motion.ul className="space-y-5 px-3">
{navlins.map((navlin, index) => (
<motion.li
custom={index}
key={index}
variants={liVariants} // Apply variants to each li item
className="text-gray-600 text-[1em] font-normal flex items-center gap-5"
>
{navlin}
{index === 0 || index === 1 ? (
<IoMdArrowDown size={24} color="gray" />
) : (
<></>
)}
</motion.li>
))}
</motion.ul>
</motion.div>
);
});