如何用CSS实现可无限嵌套的响应式导航菜单(无需新增选择器)
支持任意层级嵌套的响应式导航菜单解决方案
以下是修改后的完整代码,实现无需新增CSS选择器即可添加任意层级子菜单的功能,子菜单展开效果符合要求:
HTML 结构
<div class="logo" role="img"> <img src="Images/Logo.png" height="100" style="width:auto; float:left;" /> </div> <header class="header"> <div class="nav-menu-button"> <div class="nav-menu-line"></div> </div> <nav class="nav" data-visible="false" role="navigation"> <ul> <li> <a href="#">Home</a> </li> <li class="has-submenu"> <a href="#">Goods</a> <button class="submenu-toggle" aria-expanded="false">▼</button> <ul class="submenu"> <li><a href="#">Category 1</a></li> <li class="has-submenu"> <a href="#">Category 2</a> <button class="submenu-toggle" aria-expanded="false">▼</button> <ul class="submenu"> <li><a href="#">Subitem 1</a></li> <li><a href="#">Subitem 2</a></li> </ul> </li> </ul> </li> <!-- 可直接复制以下结构添加更多层级子菜单 --> <!-- <li class="has-submenu"> <a href="#">父菜单</a> <button class="submenu-toggle" aria-expanded="false">▼</button> <ul class="submenu"> <li><a href="#">子菜单</a></li> </ul> </li> --> </ul> </nav> </header>
CSS 样式
@font-face { font-family: "Roboto"; font-style: normal; src: url("../Fonts/Roboto.ttf") format("truetype"); } * { font-family: "Roboto"; font-size: 20px; margin: 0; padding: 0; box-sizing: border-box; direction: rtl; } .logo { z-index: 10; position: relative; } .header { background: linear-gradient(to top, #9472ff 2%, #3d00ff 98%) center/100%; border-radius: 10px; box-shadow: 1px 2px 4px #000000; color: white; width: 100%; height: 100px; padding-right: 10px; padding-top: 40px; text-align: right; text-shadow: 0px 2px 2px #000000; } /* 基础导航样式调整 */ .nav { list-style: none; display: flex; gap: 24px; } .nav ul { list-style: none; width: 100%; } .nav li { position: relative; width: 100%; text-align: center; } .nav li a { text-decoration: none; font-size: 28px; color: #f3b238; display: inline-block; width: 100%; position: relative; } .nav li a:hover { color: #fff; } .nav li a::after { content: ""; width: 0%; height: 4px; background-color: #fff; position: absolute; left: 0px; bottom: -5px; border-radius: 5px; transition: all 0.7s ease; } .nav li a:hover::after { width: 100%; } /* 通用子菜单样式 */ .nav .has-submenu { display: flex; align-items: center; justify-content: center; gap: 8px; } .nav .submenu { max-height: 0; overflow: hidden; transition: max-height 0.7s ease; padding-left: 16px; } .nav .submenu.active { max-height: 1000px; } .nav .submenu-toggle { background: none; border: none; color: #f3b238; font-size: 20px; cursor: pointer; transition: transform 0.3s ease; } .nav .submenu-toggle.expanded { transform: rotate(180deg); } @media (max-width: 769px) { .nav { position: fixed; inset: 0; background: linear-gradient(to top, #9472ff 2%, #3d00ff 98%) center/100%; flex-direction: column; align-items: center; padding-top: 70px; transform: translateX(100%); transition: all 0.7s ease; } .nav[data-visible=true] { transform: translateX(0%); } .nav-menu-button { position: absolute; display: flex; z-index: 10; justify-content: center; align-items: center; height: 80px; width: 80px; right: -10px; top: -10px; cursor: pointer; transition: all 0.7s ease-in-out; } .nav-menu-line { width: 50px; height: 6px; background-color: #d0fd66; border-radius: 5px; transition: all 0.7s ease-in-out; } .nav-menu-line::before, .nav-menu-line::after { content: ""; position: absolute; width: 50px; height: 6px; background-color: #d0fd66; border-radius: 5px; transition: all 0.7s ease-in-out; } .nav-menu-line::before { transform: translateY(-16px); } .nav-menu-line::after { transform: translateY(16px); } .nav-menu-button.open .nav-menu-line { transform: translateX(-50px); background: transparent; } .nav-menu-button.open .nav-menu-line::before { transform: rotate(45deg) translate(35px, -35px); } .nav-menu-button.open .nav-menu-line::after { transform: rotate(-45deg) translate(35px, 35px); } /* 移动端子菜单适配 */ .nav ul { display: flex; flex-direction: column; gap: 16px; } .nav .submenu { padding-top: 8px; padding-left: 32px; } }
JavaScript 逻辑
const MenuButton = document.querySelector(".nav-menu-button"); const Navigation = document.querySelector(".nav"); let MenuOpen = false; // 主菜单切换 MenuButton.addEventListener("click", () => { MenuOpen = !MenuOpen; MenuButton.classList.toggle("open", MenuOpen); Navigation.setAttribute("data-visible", MenuOpen.toString()); }); // 子菜单切换(事件委托支持任意层级) document.addEventListener('click', (e) => { if (e.target.classList.contains('submenu-toggle')) { const toggleBtn = e.target; const submenu = toggleBtn.nextElementSibling; const isExpanded = toggleBtn.getAttribute('aria-expanded') === 'true'; toggleBtn.setAttribute('aria-expanded', !isExpanded); toggleBtn.classList.toggle('expanded'); submenu.classList.toggle('active'); } });
关键特性说明
- 任意层级嵌套:通过
<ul class="submenu">嵌套在<li class="has-submenu">内,新手可直接复制该结构添加多层子菜单,无需修改CSS。 - 平滑展开动画:利用
max-height过渡实现子菜单的平滑展开/收起,替代无法动画的display: none。 - 事件委托:通过监听全局点击事件处理所有子菜单按钮,避免为每个按钮单独绑定事件,提升性能。
- 可访问性优化:使用
<button>作为子菜单触发器,添加aria-expanded属性标识展开状态。
内容的提问来源于stack exchange,提问作者Reza Jaferi
相关产品推荐
相关产品推荐

