前端焦点陷阱常用于模态框、抽屉等场景,用来把键盘焦点限制在容器内。但在实际实现中,Tab键循环时焦点异常回跳是高频问题,表现为焦点跳到容器外或错误节点,破坏无障碍体验。

问题产生的原因
焦点回跳通常不是浏览器bug,而是实现逻辑有漏洞。常见原因有以下几点:
- 可聚焦元素列表在DOM更新后未重新获取,导致索引错乱。
- 容器内有隐藏但未被过滤的元素,Tab时焦点落到了不可见节点。
- 监听keydown时未阻止默认行为,或计算下一个焦点索引出错。
基础焦点陷阱实现
下面给出一个简单的焦点陷阱示例,在容器内循环Tab键:
// 获取容器内可聚焦元素
function getFocusable(container) {
const selector = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
return Array.from(container.querySelectorAll(selector)).filter(el => el.offsetParent !== null);
}
// 绑定Tab键循环
function bindFocusTrap(container) {
const focusable = getFocusable(container);
if (focusable.length === 0) return;
container.addEventListener('keydown', function(e) {
if (e.key !== 'Tab') return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
}
避免异常回跳的关键点
动态更新列表
当容器内部节点变化后,必须重新调用getFocusable,否则旧列表会让焦点算错位置。
过滤不可见元素
使用offsetParent判断元素是否渲染,避免焦点跳到display:none的节点上。
正确处理边界
在首尾元素处用preventDefault阻断浏览器默认移动,并手动聚焦到另一端。
总结
解决Tab键循环回跳,核心在于维护准确的可聚焦元素列表并正确处理边界。按照上述方式实现,就能做出稳定的焦点陷阱。
focus_trapTab_keyaccessibility修改时间:2026-07-30 02:24:08