必须用 checkbox 因其天然支持 :checked 伪类,配合兄弟选择器 ~ 可零 js 控制菜单显隐;错误结构、display: none 会导致布局跳动,应改用 visibility/opacity/max-height 组合;媒体查询需手动重置 checkbox 状态防旋转错乱。

纯 CSS 实现导航栏折叠菜单,必须用 <input type="checkbox"> + :checked 伪类驱动,其他方式(比如 <button></button> 或 <div>)在 JS 失效时直接不可用。
<h3>为什么非得用 checkbox 而不是 button</h3>
<p>CSS 无法监听 <code><button></button> 的点击状态,也就没法靠伪类触发后续样式切换;而 <input type="checkbox"> 天然支持 :checked,配合兄弟选择器 ~ 就能精准控制 .nav-menu 显隐——这是零 JS 方案唯一可靠的底层机制。
常见错误包括:
- 把
<input>放在.nav-menu后面,:checked ~ .nav-menu根本选不到(兄弟选择器只往后找) - 用
<label></label>包裹<input>却没写for属性,iOS 触控区域可能小于 44px,点不中 - 把
<input>塞进<nav></nav>内部但结构错位,导致语义断裂、屏幕阅读器跳过开关
display: none 会导致页面“上跳”,怎么避免
直接对菜单用 display: none/display: block 切换,会触发布局重排:父容器高度瞬间归零,内容向上蹿动。这不是动画问题,是 DOM 渲染逻辑本身造成的。
正确做法是组合三个属性:
-
visibility: hidden(保持可访问性,屏幕阅读器仍能读) -
opacity: 0(配合transition实现淡入淡出) -
max-height: 0+overflow: hidden(避免撑开空间,同时保留过渡能力)
展开时设 max-height: 500px —— 注意不能用 auto,必须预估最大高度;若菜单项动态增减,要么留足余量,要么改用 JS 补充计算。
@media 断点里怎么让汉堡按钮和菜单“各司其职”
桌面端默认显示完整横向菜单,移动端才启用折叠逻辑——这个判断必须交给 CSS 媒体查询,而不是 JS。否则设备旋转时状态不同步、SSR 渲染错乱。
具体写法是:
- 默认状态下(大屏),
.hamburger和.nav-menu都设display: none - 仅在
@media (max-width: 768px)内让.hamburger显示,并让.nav-menu通过:checked ~控制显隐 - 菜单内部用
flex-direction: column+width: 100%确保垂直铺满,别留白边
特别注意:设备旋转后,<input> 的勾选状态不会自动同步到新尺寸下的媒体查询逻辑里。它还是“已勾选”,但对应菜单可能已被隐藏。这时候必须监听 window.matchMedia 变化,手动重置 input.checked = false,否则用户会卡在“看不见菜单却以为它开着”的状态。
子菜单在移动端怎么处理才不乱
二级菜单(如悬停展开的下拉项)在移动端折叠模式下不该存在,但很多人忘了关掉。结果是点击汉堡后,一堆横向排列的 <li> 堆在一起,或者 position: absolute 的子菜单飞出视口。
解决方式很直接:
- 所有子菜单(
.submenu或嵌套<ul></ul>)在移动端统一设display: none - 如果需要移动端也支持多级展开,必须改用 JS 控制(因为
:hover在触摸设备不可靠),且每个子项要单独绑定click事件并加aria-expanded - 检查
padding/margin是否被父级 flex 的gap覆盖,移动端建议用padding-block替代padding-top/padding-bottom
真正难的不是让菜单“动起来”,而是让它在 JS 失效、屏幕阅读器启用、键盘导航、缩放 200%、甚至网络极慢时,依然能被用户识别、抵达、操作——这些细节藏在 HTML 结构顺序、CSS 选择器精度和属性语义里,而不是动画时长或图标旋转角度中。











