HTML结构必须严格表达父子关系,否则CSS选择器失效;.submenu必须是.has-submenu的直接子元素,不能隔层;移动端汉堡菜单需确保checkbox在label前、id与for匹配、不可用display:none隐藏;动画应使用max-height而非display:none;aria-expanded必须与实际状态同步,包括设备旋转时的兜底校准。

HTML结构必须表达父子关系,否则CSS选择器失效
子菜单不展开、点击无反应,大概率不是JS没写对,而是
- 没被正确包在
- 里。CSS里写的
.has-submenu.active > .submenu要求.submenu是.has-submenu的**直接子元素**,平级写法(比如所有li并列,.submenu放在外面)会让这个选择器完全不匹配。常见错误结构:
<li>首页</li> <li>产品</li> <ul class="submenu"><li>Web</li></ul>
正确结构必须是:
<li class="has-submenu"> 产品 <ul class="submenu"> <li>Web</li> </ul> </li>-
.has-submenu和.submenu之间不能隔层容器(如div) - 如果用
<details><summary>,<summary>必须是<details>的第一个子元素,否则语义断裂 - 多个导航区(主导航、页脚导航)必须各自独立用
<nav>,不能套在一个<nav>里
移动端汉堡菜单依赖的DOM顺序
纯CSS方案里,
<input type="checkbox">不是装饰,它是状态源。它的位置和绑定方式直接决定点击是否生效。立即学习“前端免费学习笔记(深入)”;
必须满足三个硬性条件:
-
<input>必须出现在<label>之前(或被<label>包裹),否则:checked ~ ul这类兄弟选择器失效 -
<label>的for属性值必须与<input>的id严格一致,大小写、拼写、空格都不能错 -
<input>不能用display: none隐藏——这会让它脱离可访问树;应改用position: absolute; opacity: 0; pointer-events: none
错误示例:
<label for="toggle">☰</label><input id="toggle" type="checkbox">→ 点击图标无效,因为~选择器只匹配后续兄弟元素,而<input>在<label>之后。响应式切换不是靠display: none,而是靠max-height过渡
用
display: none控制菜单显隐,会导致transition动画完全失效——浏览器无法对“从无到有”的尺寸做插值计算。真正能动画的属性是max-height、transform或opacity。推荐写法(配合
<input>状态):.nav-menu { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } input:checked ~ .nav-menu { max-height: 300px; /* 必须是具体数值,不能是none */ }-
max-height值要略大于所有子项总高度,太小会截断,太大则动画拖沓 - 别单独用
visibility: hidden——它不占空间但允许点击穿透,用户会点透到下层元素 -
opacity可以加,但必须配合pointer-events: none禁用交互,否则动画中途仍可点击
aria-expanded和实际状态不同步是键盘用户的致命缺陷
屏幕阅读器靠
aria-expanded判断菜单当前是展开还是收起。如果JS切换了class但忘了同步这个属性,键盘用户按Enter打开菜单后,读屏器仍会说“已折叠”,导致操作困惑甚至失败。无论用哪种方案,都必须保证状态一致性:
- 用
<details>时,浏览器自动管理open属性和aria-expanded,无需手动干预 - 用
<input type="checkbox">时,aria-expanded应绑定到触发按钮(如<button>或<label>),值为true或false,且随:checked状态实时更新 - 用JS切换时,必须同时操作DOM类名和
aria-expanded属性,不能只改样式
最易忽略的一点:设备旋转时,
window.matchMedia不会自动触发状态重置,aria-expanded可能残留旧值——需要监听orientationchange或resize事件做兜底校准。 -



















