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

HTML结构必须表达父子关系,否则CSS选择器失效
子菜单不展开、点击无反应,大概率不是JS没写对,而是
-
.has-submenu和.submenu之间不能隔层容器(如div) - 如果用
<details><summary></summary></details>,<summary></summary>必须是<details></details>的第一个子元素,否则语义断裂 - 多个导航区(主导航、页脚导航)必须各自独立用
<nav></nav>,不能套在一个<nav></nav>里
移动端汉堡菜单依赖的DOM顺序
纯CSS方案里,<input type="checkbox">不是装饰,它是状态源。它的位置和绑定方式直接决定点击是否生效。
必须满足三个硬性条件:
-
<input>必须出现在<label></label>之前(或被<label></label>包裹),否则:checked ~ ul这类兄弟选择器失效 -
<label></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></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></details>时,浏览器自动管理open属性和aria-expanded,无需手动干预 - 用
<input type="checkbox">时,aria-expanded应绑定到触发按钮(如<button></button>或<label></label>),值为true或false,且随:checked状态实时更新 - 用JS切换时,必须同时操作DOM类名和
aria-expanded属性,不能只改样式
最易忽略的一点:设备旋转时,window.matchMedia不会自动触发状态重置,aria-expanded可能残留旧值——需要监听orientationchange或resize事件做兜底校准。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











