桌面端好好的多级菜单,搬到手机上就点不开 📱。原因很直接:触屏没有悬停状态,li:hover 这套展开逻辑在移动端失效。改造思路不是推倒重写,而是把 hover 与 click 两套触发方式并存。

第一步:结构先语义化
<nav>
<ul class="menu">
<li><a href="/a">一级</a>
<ul class="sub"><li><a href="/a/1">二级</a></li></ul>
</li>
</ul>
</nav>
嵌套 ul 放在父级 li 内部,屏幕阅读器才能正确读出层级。
第二步:桌面端保留 hover
@media (hover: hover) {
.menu li:hover > .sub { display: block; }
}
hover: hover 媒体查询只在真正支持悬停的设备上生效 🖱️,避免触屏首次点击被解释成 hover 而不跳转。
第三步:触屏端改为 click 切换
给含子菜单的一级项绑定点击事件:首次点击展开子菜单并阻止跳转,再次点击才放行。同时维护 aria-expanded,屏幕阅读器依赖它播报展开状态。注意事件冒泡 ⚠️:点击子项时不应把事件传到父级触发折叠,必要时调用 stopPropagation()。
第四步:键盘可达性补全
.menu li:focus-within > .sub { display: block; }
focus-within 让 Tab 键进入子菜单时保持展开 ⌨️,键盘用户与鼠标用户获得一致体验。
收尾验证
桌面 hover、触屏 click、键盘 Tab 三条路径各测一遍 ✅。三条路径共用同一份结构,差异只在触发层,这就是渐进增强的标准做法。
