bootstrap 5 多级菜单手机点击即收,因其未初始化二级 dropdown-menu 的 js 事件,仅依赖桌面端 hover;需手动调用 getorcreateinstance 并延迟 hide、强化 aria 与键盘支持。

为什么 Bootstrap 5 的多级菜单在手机上点一下就收?
因为 Bootstrap 5 官方压根没给二级 dropdown-menu 注册点击逻辑——data-bs-toggle="dropdown" 只作用于第一层,子菜单的 .dropdown-menu 根本没被 JS 初始化,纯靠 CSS :hover 触发,而触摸设备没有 hover。这不是你漏写了属性,是框架主动放弃支持。
常见现象:.dropdown-item 点击后整个下拉直接消失;或者二级菜单只在桌面悬停有效,手机点开即关。根源在于移动端依赖 click + show/hide 控制,但默认没绑定。
- 必须为每个二级触发项(比如
<li class="dropdown-submenu">)单独调用bootstrap.Dropdown.getOrCreateInstance() - 不能复用一级菜单的实例,否则
hide()会误杀兄弟菜单 - 监听
click而非mouseenter,确保触屏可用 - 同时保留
focus和keydown监听,否则键盘用户进不去二级
如何让二级菜单向右展开且不飞出屏幕?
默认所有 .dropdown-menu 都向下定位,二级想贴着父项右边展开,不能靠 data-bs-popper="right"(Bootstrap 5 不识别这个属性),得用 CSS 强制定位,否则子菜单会出现在左上角或页面外。
关键不是加 left: 100%,而是解决三层错位:父 .dropdown-item 缺少 position: relative → 子 .dropdown-menu 相对 body 定位 → 再嵌套就彻底脱标。
-
.dropdown-item必须设position: relative -
.dropdown-menu .dropdown-menu设position: absolute; top: 0; left: 100%; margin-top: -2px; - 删掉 Bootstrap 默认加的
opacity: 0和visibility: hidden,改用display: none控制显隐,否则 transition 失效 -
z-index至少设为1001,否则会被同级一级菜单遮住
三级菜单鼠标一移就闪退,怎么留出穿越间隙?
二级展开后,鼠标从一级移到二级再滑向三级,二级菜单瞬间关闭——这是 Bootstrap 的 mouseleave 事件没做区域判断,直接触发了隐藏。不是 CSS 写错了,是事件响应太快。
别用 data-bs-popper="none"(已废弃),也别指望媒体查询能解决,核心是控制 hide 的时机。
- 给二级
.dropdown-menu的父容器加自定义类名,如dropdown-submenu - 监听该容器的
mouseenter,调用dropdown.show() - 监听
mouseleave时用setTimeout(() => dropdown.hide(), 200)延迟关闭 - 三级菜单必须是二级
.dropdown-menu的**直接子元素**,不能是兄弟节点,否则定位和事件流全乱
键盘导航和屏幕阅读器支持最容易被忽略的点
纯 :hover + 手写 JS 的方案,大概率让 Tab 键卡在一级菜单进不去二级,Esc 按了也没反应,这违反 WCAG 2.1,上线后可能被投诉无障碍问题。
光加 aria-expanded 不够,必须同步更新状态,且焦点要能自然流入子菜单项。
- 每个二级触发项(
<button></button>或<a></a>)加aria-haspopup="true"和aria-expanded="false" - 展开时动态设
aria-expanded="true",收起时设"false" - 子菜单容器加
role="menu",每个子项加role="menuitem" - 监听
keydown:方向键在子菜单内切换,Esc逐级收起(不是一键全关)
真正麻烦的不是让菜单显示出来,而是让它既能在触屏点开、又能让键盘用户顺畅操作、还能被屏幕阅读器正确读出层级关系——这三者缺一不可,少一个都算半成品。











