焦点陷阱是确保模态框内键盘导航不逃逸的可访问性机制;它通过动态收集合法可聚焦元素、拦截tab键在首尾间循环跳转、并精准恢复关闭前焦点来满足wcag 2.1 aa要求。

什么是 focus trap,为什么模态框必须用它
不加 focus trap 的模态框,用户按 Tab 键很容易跳出弹窗,落到背景页面的按钮或输入框上——这不仅是体验问题,更是 WCAG 2.1 AA 级可访问性硬性要求。焦点陷阱本质是监听 Tab(含 Shift + Tab),在模态框内首尾元素之间循环跳转,不让焦点“逃逸”。
用 focusableElements 列出所有可聚焦节点,别只靠 querySelectorAll('input, button, a, [tabindex]')
常见错误是漏掉 [tabindex="0"] 或 contenteditable 元素,或者把 display: none 或 visibility: hidden 的元素也纳入。正确做法:
- 用
Array.from(modal.querySelectorAll('*'))获取全部子元素,再逐个判断:el.offsetParent !== null && el.tabIndex >= 0 && !el.hasAttribute('disabled') - 排除
aria-hidden="true"的祖先节点下的所有子元素(可用el.closest('[aria-hidden="true"]')检查) - 注意:
<details></details>展开时内部<summary></summary>才可聚焦,需动态更新列表
监听 keydown 拦截 Tab,但别用 event.preventDefault() 直接阻止所有 Tab
直接 preventDefault() 会破坏屏幕阅读器对 Tab 的语义处理(比如 NVDA 在表单中依赖原生行为)。正确逻辑是:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 只在焦点即将离开模态框时干预:当当前聚焦元素是最后一个可聚焦项、且用户按
Tab;或当前是第一个、且按Shift + Tab - 用
event.preventDefault()+nextElement.focus()主动切换,而非阻止后靠浏览器默认行为 - 务必在模态框
show后立即调用firstFocusableElement.focus(),否则键盘用户第一次按Tab会落到地址栏
关闭模态框时要手动恢复焦点,别依赖 autofocus
autofocus 在 Safari 和部分旧版 Chrome 中不可靠,且无法指定关闭后该回到哪个具体元素(比如用户是从「编辑资料」按钮点开的,关掉后理应回到该按钮)。实操建议:
- 打开模态框前,用
const returnFocus = document.activeElement记录当前焦点元素 - 关闭时检查
returnFocus是否仍存在于 DOM 且可聚焦(returnFocus?.offsetParent && returnFocus?.tabIndex >= 0),满足则returnFocus.focus() - 若原元素已销毁(如表格行被删),退而求其次聚焦触发按钮(需提前存
triggerEl引用)或页面主内容区main元素
focus trap 不是加个库就完事——可聚焦元素范围、Tab 拦截时机、关闭后焦点归位,三者任意一环出错,都会让键盘用户卡死或迷失。尤其注意动态渲染内容(如加载后插入的按钮)必须重新计算焦点列表。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










