所以,我对如何在 Next.js 中实现响应式移动导航有点困惑。我的问题源于通过 Javascript 和 CSS 处理菜单切换状态。
我的导航菜单遵循 Log Rocket 的 以下教程,但教程中使用的方法是为 React 编写的,没有考虑 Next.js 如何应用 CSS 类名。简单的 React 示例依赖于动态更新 CSS,而大多数 Next.js 方法似乎都建议使用 useState 来切换元素。这可行,但我失去了能够通过媒体查询和切换按钮控制导航的粒度。
我当前的实现检查布尔 useState 值来显示菜单,我只想将其应用于 780px 或更低的菜单。如果我从
false
状态开始,则不会呈现整个菜单,包括切换菜单的按钮。如果我从 true
状态开始,那么移动菜单将呈现为自动展开,这是我想防止的。当用户不在移动设备上时,这也会导致切换完全删除菜单(例如,如果菜单未切换,并且用户使用大屏幕尺寸。
那么,问题就变成了如何防止这种意外行为?或者如何将移动菜单默认状态设置为隐藏,同时保留点击切换功能?
import { useState } from "react";
import Link from "next/Link";
import SiteLogo from "../components/CompLogo";
import navStyles from "../styles/Nav.module.css";
const Nav = () => {
const [isNavExpanded, setIsNavExpanded] = useState(true);
return (
<div>
<nav className={navStyles.nav}>
<SiteLogo />
<button
className={navStyles.hamburger}
onClick={() => {
setIsNavExpanded(!isNavExpanded);
console.log("clicked");
console.log(isNavExpanded);
}}
>
{/* icon from heroicons.com */}
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110
2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0
01-1- 1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
/>
</svg>
</button>
<div className={navStyles.navigation}>
{isNavExpanded && (
<ul className={navStyles.menu}>
<li className={navStyles.links}>
<Link href="/">
<a className={navStyles.anchors}>Home</a>
</Link>
</li>
<li className={navStyles.links}>
<Link href="/map">
<a className={navStyles.anchors}>Map Page</a>
</Link>
</li>
<li className={navStyles.links}>
<Link href="/about">
<a className={navStyles.anchors}>About</a>
</Link>
</li>
</ul>
)}
</div>
</nav>
</div>
);
};
导出默认导航
这是我的CSS:
.one {
margin-top: 0;
height: 100vh;
width: 100%;
background-image: url("../../public/images/My\ project.jpg");
background-position: top;
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: left;
align-items: center;
}
.oneAppointment {
width: 55%;
margin-left: 10%;
text-align: left;
/* display: flex; */
/* justify-content: center; */
/* flex-direction: column; */
/* height: 40%; */
/* background-color: #fff; */
/* padding: 20px 40px; */
/* border-radius: 6px; */
}
.oneAppointment h1 {
font-size: 40px;
font-weight: 700;
/* color: #000; */
}
.abc {
font-size: 56px;
font-weight: 700;
/* color: blanchedalmond; */
}
.heading {
font-size: 40px;
font-weight: 700;
/* background-color: blueviolet; */
margin-bottom: 10px;
/* color: #000; */
}
.designer a {
margin-top: 16px;
}
.designer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 520px;
width: 100%;
background-image: url("../../public/images/fas.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
/* background-color: blueviolet; */
}
.blogs {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 520px;
width: 100%;
background-image: url("../../public/images/fas.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
/* background-color: blueviolet; */
}
.collection {
margin-top: 0;
height: 90vh;
width: 100%;
/* background-image: url("./website/images/pexels-yan-krukov-4458418.jpg"); */
background-image: url("../../public/images/collec.jpg");
background-position: top;
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
.collections {
width: 45%;
margin-left: 45%;
text-align: center;
margin-top: -100px;
}
.shop {
margin-top: 0;
height: 90vh;
width: 100%;
/* background-image: url("./website/images/pexels-yan-krukov-4458418.jpg"); */
background-image: url("../../public/images/shopp.jpg");
background-position: top;
background-repeat: no-repeat;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
}
.shopContent {
width: 45%;
margin-left: 45%;
text-align: center;
margin-top: -100px;
}
@media only screen and (min-width: 1px) and (max-width: 768px) {
.heading {
font-size: 36px;
font-weight: 700;
margin-bottom: 10px;
}
.one {
margin-top: 0;
background-image: url("../../public/images/mmy.jpg");
background-position: left;
}
.oneAppointment {
width: 30%;
margin-left: 5%;
text-align: left;
margin-top: -80px;
}
.oneAppointment h1 {
font-size: 28px;
font-weight: 700;
line-height: 55px;
}
.abc {
font-size: 40px;
font-weight: 700;
/* color: blanchedalmond; */
}
.designer {
text-align: center;
height: 600px;
width: 90%;
margin-left: 5%;
background-position: left;
background-size: cover;
}
.blogs {
margin-top: 100px;
text-align: center;
height: 600px;
width: 90%;
margin-left: 5%;
background-position: left;
background-size: cover;
}
.collection {
/* margin-top: 0;
height: 90vh;
width: 100%; */
/* background-image: url("./website/images/pexels-yan-krukov-4458418.jpg"); */
/* background-image: url("../public/images/collec.jpg"); */
background-image: url("../../public/images/colle.jpg");
background-position: left;
/* background-position: calc(-10px); */
/* background-repeat: no-repeat;
background-size: cover;
display: flex; */
justify-content: left;
/* align-items: center; */
}
.collections {
width: 70%;
margin-left: 25%;
/* background-color: aquamarine; */
text-align: right;
margin-top: -140px;
}
.shop {
margin-top: -100px;
/* margin-top: 0;
height: 90vh;
width: 100%; */
/* background-image: url("./website/images/pexels-yan-krukov-4458418.jpg"); */
/* background-image: url("../public/images/shopp.jpg"); */
background-position: calc(-280px);
/* background-repeat: no-repeat; */
/* background-size: cover; */
/* display: flex; */
justify-content: left;
/* align-items: center; */
}
.shopContent {
width: 70%;
margin-left: 25%;
/* background-color: chartreuse; */
text-align: right;
margin-top: 0px;
}
}
.nav {
background-color: white;
color: black;
width: 100%;
display: flex;
align-items: center;
position: relative;
padding: 0.5rem 0 rem;
}
.navigation {
margin-left: auto;
}
.menu {
display: flex;
padding: 0;
}
.links {
list-style-type: none;
margin: 0 1rem;
}
.links a {
text-decoration: none;
display: block;
width: 100%;
}
.hamburger {
border: 0;
height: 80px;
width: 80px;
padding: 0.5rem;
border-radius: 50%;
background-color: white;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
position: absolute;
top: 50%;
right: 5px;
transform: translateY(-50%);
display: none;
}
@media screen and (max-width: 768px) {
.hamburger {
display: block;
}
.menu {
position: absolute;
top: 146px;
left: 0;
flex-direction: column;
width: 100%;
height: calc(100vh - 147px);
background-color: white;
border-top: 1px solid black;
}
.links {
text-align: center;
margin: 0;
}
.links a {
color: black;
width: 100%;
padding: 1.5rem 0;
}
.links:hover {
background-color: #eee;
}
.menu.expanded {
display: block;
}
}
不遵循您的代码,而是一般指南 -
*重要 - 这仅供您理解,可能与您的代码不完全相同。
对于您来说,宽度是780px,您要检查和设置移动菜单和桌面菜单。
第 1 步 - 您必须检查屏幕的宽度,您可以使用 CSS 媒体屏幕宽度来执行此操作,但随后您的组件将加载移动菜单和桌面菜单的所有数据,因此您可以编写一个事件监听器宽度改变并做一些事情,(假设宽度是一种状态)-
{width > 780px ? <DesktopMenu /> : <MobileMenu /> }
现在您可以延迟加载这两个组件。
现在来到移动菜单部分 -
“打开菜单抽屉按钮”默认情况下应始终在移动屏幕上可见(切换按钮打开)。
在 openMenuDrawerButton 上编写一个点击事件来打开一个实际保存菜单布局的抽屉,并使用 CSS 实现幻灯片效果或其他任何东西。
再次使用状态来检查抽屉是否打开,让我们将此状态称为 visibleDrawer,然后使用 openMenuDrawerButton {visibleDrawer : true} 以及抽屉本身中存在的关闭按钮切换此状态。
{!visibleDrawer && < OpenMenuDrawerButton /> }
单击 opendrawer 按钮(打开抽屉)后隐藏它。
如果您需要更多解释,请写信。
我使用以下技巧来使用 CSS 媒体查询隐藏/显示适当的元素,该查询在设置任何状态之前运行(因此是“默认”行为)。我使用MaterialUI,希望你能适应你的需求:
{/* Mobile Version */}
<Hidden smUp implementation="css">
...
</Hidden>
{/* Desktop Version */}
<Hidden smDown implementation="css">
...
</Hidden>
您可以在 useState 中应用其他逻辑,以实现默认加载后所需的任何设置。