Safari 中 dialog.showModal() 无法解决焦点逃逸,需手动聚焦首个元素、拦截 Tab 键循环、关闭后恢复触发源焦点;嵌套时须分层管理焦点与监听器;非模态场景应避免使用 dialog,改用 inert + aria-hidden 双保险控制可访问性。

dialog.showModal() 不能自动解决 Safari 的焦点逃逸问题
哪怕你用了 showModal(),在 Safari(包括 iOS/macOS 全版本)中,Tab 键仍可能跳到页脚按钮、导航栏链接甚至 body 上——这不是体验差,是焦点管理彻底失效。原生 API 只设置了 aria-modal="true" 和 ::backdrop,但没真正围住焦点流。
必须手动补三件事:
- 打开后立刻用
requestAnimationFrame(() => firstFocusable.focus())聚焦首个可聚焦元素(不能等 CSS 动画结束) - 监听
keydown拦截Tab和Shift+Tab,在对话框内手动循环(别用:focusable,浏览器不支持) - 关闭前缓存触发源(如
open-btn),关闭后检查它是否还存在且可聚焦,再调用.focus()
嵌套 dialog 时焦点必须分层管理
Modal-1 打开后再点按钮弹出 Modal-2,键盘用户按 Esc 应只关闭 Modal-2,Tab 键也绝不能回到 Modal-1 的按钮上——浏览器不会自动维护这种上下文。
实操要点:
- 每个
实例需独立维护自己的可聚焦元素列表和当前焦点索引 - 打开新 dialog 时,先暂停上一层的
trapTabKey监听器,再激活新一层的 - 关闭子 dialog 后,立即恢复父 dialog 的焦点状态(不是简单调
parentDialog.focus(),而是还原上次离开时的聚焦位置) - 避免对
document绑定全局keydown,改用事件委托到各根节点上
非模态场景硬套 反而破坏无障碍
想做侧边配置面板或实时预览浮层?别用 show()。它不创建遮罩、不拦截 Tab、不响应 Esc,Safari 还会错误宣告“模态已开启”,导致屏幕阅读器逻辑混乱。
更稳妥的做法:
- 用 替代
,语义正确且行为可控- 手动控制
inert属性开关背景交互范围(比aria-hidden更现代、更可靠)- 需要拖拽/resize/minimize?
不提供这些能力,强行套用只会增加语义冗余和兼容负担- 若必须保留
标签,得立刻补:调dialog.focus()后马上document.body.focus(),否则焦点卡空节点aria-modal="true" 不足以替代 aria-hidden 控制背景
在旧版 Firefox 和部分 Safari 版本中,
aria-modal="true"会被忽略,屏幕阅读器仍会朗读背景内容。单靠这个属性,等于没锁住信息流。双保险方案:
- 给包裹主内容的容器(比如 )动态加
aria-hidden="true",而不是直接操作- 打开 dialog 时同步设置:
appEl.setAttribute('aria-hidden', 'true')+dialog.setAttribute('aria-modal', 'true')- 关闭时严格按顺序还原:
dialog.removeAttribute('aria-modal')→appEl.removeAttribute('aria-hidden')→ 再恢复焦点- 注意:如果页面有多个并列内容区(如 aside + main),要分别控制,不能一刀切
焦点管理最易被忽略的点,不是“怎么聚焦”,而是“什么时候该停止聚焦”——比如关闭前未清理监听器,或嵌套时未暂停上层逻辑,会导致事件堆积、焦点错乱、内存泄漏。这些在自动化测试里很难覆盖,必须靠人工走查键盘流。 - 打开 dialog 时同步设置:
- 手动控制
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











