应为多级菜单每个可展开项添加语义化按钮(如或role="button"的),通过aria-expanded和aria-haspopup明确状态,用nextelementsibling精准控制对应子菜单,阻止事件冒泡,并在媒体查询中仅对精细指针设备启用:hover。

怎么给多级菜单加触发按钮而不是纯:hover
纯 :hover 在手机上根本不可用,点一下就收,不是 bug 是规范限制。必须用按钮(<button></button> 或带 role="button" 的 <a></a>)显式控制展开/收起状态。
常见错误是直接把 <li> 当成可点击区域,但 <li> 默认不可聚焦、不响应键盘 Enter/Space,屏幕阅读器也读不出它是操作控件。
- 每个可展开项必须有明确的触发按钮,比如
<button class="dropdown-toggle" aria-expanded="false" aria-haspopup="true">Products</button> - 按钮后紧跟子菜单容器:
<ul class="submenu" role="menu" aria-hidden="true">...</ul> - 不要用
<div> 或 <code><span></span>冒充按钮——它们没有默认交互语义,也不能被键盘Tab到如何让按钮正确控制对应层级的子菜单
关键在 DOM 结构和选择器精度。如果所有子菜单都用同一个 class(比如
.submenu),JS 一调用document.querySelectorAll('.submenu')就全中招,导致点一级菜单,三级全开。推荐结构:每个可展开项包裹自己的按钮 + 直接子
<ul></ul>,用>子选择器限定作用域: - JS 绑定事件时,只查当前按钮的下一个兄弟元素:
button.nextElementSibling(必须是<ul></ul>) - 避免用
querySelector('.submenu')—— 它会跨层级找,破坏隔离性 - 如果子菜单 class 不统一(如
.submenu和.nested-menu),用querySelector('.submenu, .nested-menu')更安全 - 在按钮 click handler 里加
e.stopPropagation(),防止向上冒泡 - 子菜单内部的可点击元素(
<a></a>、<button></button>)也要单独监听 click,并调用e.stopPropagation() - 别在
document上全局监听 click 关闭菜单,除非你精确判断event.target是否在菜单外 —— 否则容易误关 - CSS 中写:
@media (hover: hover) and (pointer: fine) { .dropdown-toggle:hover + .submenu { display: block; } } - 这样只有鼠标设备才启用 hover 效果,触摸设备自动忽略
- 千万别删掉
:hover规则再补一堆 JS 模拟——既增加复杂度,又破坏渐进增强
为什么点击子菜单项会意外关闭父菜单
这是事件冒泡没处理干净的典型表现:点“Frontend”链接时,click 事件从 <a></a> 冒泡到外层 <li>,再触发父级 toggle 逻辑,结果刚点开就关了。
移动端适配时要不要保留 :hover 样式
要保留,但得用媒体查询覆盖。桌面端靠 :hover 提供快速预览,移动端用户手指悬停时间极短,:hover 几乎不生效,但样式本身不影响 JS 控制逻辑。
最易被忽略的是:按钮的 aria-expanded 状态必须和菜单实际显隐严格同步,否则屏幕阅读器会报错或跳过内容。每次 JS 切换 display 或 max-height,都要手动更新这个属性。











