dialog.showmodal() 不自动管理焦点围栏,需手动聚焦首个可聚焦元素、监听focusin实现tab循环、关闭后按标识符恢复焦点;选择器用button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"]),且聚焦须在requestanimationframe中执行。

dialog.showModal() 不会自动完成焦点围栏
它只启动视觉模态(backdrop、z-index、Esc 响应),不接管键盘焦点流。Safari 全版本、旧 Edge、部分 Chrome 场景下,document.activeElement 仍为 body,Tab 键直接穿透到页脚或地址栏——这不是“没聚焦”,是焦点管理压根没启动。
必须手动做三件事,缺一不可:
- 打开后立刻聚焦首个可聚焦子元素(用
requestAnimationFrame包裹,避免 DOM 渲染未完成导致querySelector返回null) - 监听
keydown拦截 Tab/Shift+Tab,在对话框内手动循环(不能监听document,必须绑定在<dialog></dialog>元素上) - 关闭前缓存触发源(如点击的
<button></button>),关闭后检查它是否仍在 DOM 中且可聚焦,再调.focus()
怎么安全获取并聚焦第一个可聚焦子元素
别用 querySelector(':focusable')——Safari 不支持该伪类;也别只查 button 或 input,会漏掉带 tabindex="0" 的容器和 <a href></a> 链接。
正确选择器是:button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])。聚焦动作必须包在 requestAnimationFrame 里:
dialog.addEventListener('open', () => {
requestAnimationFrame(() => {
const first = dialog.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (first) first.focus();
else {
dialog.tabIndex = -1;
dialog.focus();
}
});
});
若内容异步加载(如 fetch 后插入表单),不能在 showModal() 后立刻查 DOM,得等渲染完成或监听 dialog:open(需 polyfill)。
Tab 键循环必须监听 focusin 而非 keydown
监听 keydown 拦 Tab 会破坏 NVDA/JAWS 的内置导航键(如 Insert+F7 列表)、干扰 VoiceOver 方向键模式,属于反模式。
正确做法是监听 focusin,只在焦点即将离开容器时干预:
- 预收集所有合法可聚焦项:
Array.from(dialog.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')),存为实例属性 - 监听
dialog的focusin事件:若event.target不在列表中,说明焦点从外部进入,立即first?.focus() - 当
event.target === last && !event.shiftKey,或event.target === first && event.shiftKey,才执行折返并preventDefault() -
dialog.close()前必须移除该监听器,否则多层弹窗叠加时互相干扰
关闭后焦点没回原处?问题出在缓存方式
闭包里直接存 document.querySelector('#trigger-btn') 返回的节点,SPA 路由切换或组件重渲染后,该节点很可能已从文档中移除,.focus() 静默失败且无报错。
更可靠的做法是:
- 打开前用
triggerEl.getAttribute('id')或triggerEl.dataset.focusId存标识符,而非 DOM 引用 - 关闭后用
document.querySelector动态查找,再检查.matches(':enabled:visible')等可聚焦前提 - 若找不到或不可聚焦,退而聚焦到
document.body或页面主入口(如#main-content)
嵌套 <dialog></dialog> 时尤其要注意:每层必须独立维护自己的可聚焦列表和上次离开时的焦点索引,不能简单调 parentDialog.focus() —— Safari 下它大概率不会生效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











