键盘菜单卡顿失焦源于焦点管理断层,需同步dom与视觉状态:用inert替代display:none,统一监听role="tree"容器的键盘事件,收起时重置焦点并原子化更新aria-expanded、inert、tabindex及焦点位置。

为什么键盘操作菜单会卡顿或失焦
不是代码慢,是焦点管理断层。常见现象包括:按方向键后焦点“跳飞”到页面其他区域、收起子菜单时焦点消失、Tab 键意外进入已隐藏的 li 项里。根本原因在于 DOM 状态(aria-expanded、tabindex)和视觉状态(display: none、inert)没同步,浏览器仍把不可见元素当可聚焦目标。
必须用 inert 替代 display: none 隐藏子菜单
display: none 只是视觉隐藏,元素仍在可访问性树中,Tab 或屏幕阅读器仍可能聚焦它。而 inert 是原生属性,能真正让元素及其所有后代退出焦点流和辅助技术遍历。
- 展开时移除
inert,并设tabindex="0"给第一个可操作子项 - 收起时立即加
inert,同时清除所有子项的tabindex - Safari 旧版不支持
inert?补 fallback:tabindex="-1"+aria-hidden="true",但注意两者必须同时设置才有效
方向键导航必须绑定到 role="tree" 容器,而非单个 li
把 keydown 监听器挂在最外层 ul[role="tree"] 上,统一处理所有键盘逻辑。否则每个 li 单独监听会冲突,尤其在快速连按时容易漏事件或触发多次。
- 上/下键:按
aria-level和aria-posinset跳转到同级下一个/上一个treeitem - 左键:对展开态节点调用
el.toggleAttribute('aria-expanded'),并立即将焦点移回父项 - 右键:仅对未展开且含子项的节点生效,展开后焦点自动落到第一个子
treeitem - Enter/Space:行为完全一致,都触发展开/折叠,不要分开写两套逻辑
移动端点击后焦点丢失的修复点
真机测试时,点开子菜单再点空白处收起,接着按 Tab,焦点常卡在 body 开头或直接“消失”。这不是样式问题,是收起动作没重置焦点位置。
- 每次收起任意子菜单前,记录当前激活项(
document.activeElement) - 收起完成后,若焦点不在可见菜单内,手动
focus()回最近的父treeitem - 禁止用
querySelectorAll('.open')查找活跃项——改用document.querySelectorAll('[aria-expanded="true"]'),避免 class 名变更导致逻辑断裂
aria-expanded、inert、tabindex、焦点位置,这四者必须在一次 DOM 更新中同步到位。少一步,键盘用户就会卡住。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











