移动端二级菜单失效的根源在于结构与事件处理错配:hover不适用于触屏,click未阻止冒泡,子菜单类名不统一,dom缺少has-submenu标记,且缺乏aria支持和渐进增强。

移动端点不开二级菜单?不是 JS 写错了,是结构和事件处理根本没对上——hover 在触屏上不生效,click 事件又没阻止冒泡,子菜单类名不统一,DOM 层级没用 has-submenu 显式标记,这些才是真问题。
为什么触屏设备上点击没反应
桌面端靠 :hover 触发子菜单显示,但 iOS 和 Android 浏览器里 :hover 只在模拟鼠标时短暂闪现(比如 Safari 点一下),不能作为交互依据。更关键的是:很多 JS 代码只查 .submenu,但你的深层菜单实际用的是 .nested-menu 或 .dropdown-menu,导致 menuItem.querySelector(".submenu") 直接返回 null。
- 所有可展开项必须显式带
class="has-submenu"或data-has-submenu,别依赖后代选择器猜层级 - 子菜单统一用
class="dropdown-menu",CSS 里用.has-submenu > .dropdown-menu控制定位,避免类名碎片化 - HTML 结构必须支持递归遍历:子
<ul></ul>必须是父<li>的直接子元素,不能平级或外置
如何正确绑定点击事件并防止意外收起
点开“Tasty”结果一级菜单立刻关闭,这不是 bug,是事件冒泡没拦住。点击子项时,事件会一路向上冒泡到父级 <li class="has-submenu">,触发它的收起逻辑。
- 每个点击事件监听的第一句必须是
e.stopPropagation() - 不要用
document.addEventListener("click", ...)全局监听来收起菜单——它和逐级点击逻辑冲突,改用focusout+tabindex更稳 - 键盘用户需要支持
Enter和Space键展开,别只绑click
JS 失效时菜单还能用吗
脚本加载失败、被广告拦截器屏蔽、或用户禁用 JS,菜单就彻底不可见?那说明你没做渐进增强。纯 CSS 方案虽无动画,但能保底可用。
- 用
input[type="checkbox"]做开关,配合~兄弟选择器控制.nav-menu显隐,这是唯一 JS 失效时仍可用的方案 -
label必须包裹该input或通过for关联,确保 iOS 触控区域 ≥ 44px - 想加过渡动画?别用
display: none,改用max-height+overflow: hidden,否则动画不会生效
ARIA 属性不是锦上添花,是硬性要求
没有 aria-haspopup 和 aria-expanded,屏幕阅读器完全识别不出这是可展开菜单,视障用户会直接跳过整个导航区。
- 每个含子菜单的
<li>里的触发元素(<a></a>或<button></button>)必须带aria-haspopup="true"和aria-expanded="false" - 展开/收起时,JS 必须同步更新
aria-expanded值为"true"或"false" - 子
<ul></ul>加role="menu",子项<li>加role="menuitem"
真正难的不是让菜单“动起来”,而是它在 JS 失效、缩放 200%、键盘 Tab 导航、甚至网络极慢时,依然能被用户抵达、识别、操作——这些藏在 HTML 结构顺序、CSS 选择器精度和 ARIA 属性里,而不是 transition 时长中。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











