原生 需手动实现焦点管理:showmodal() 后用 requestanimationframe 延迟聚焦首个可聚焦元素(跳过 tabindex="-1"),tab 循环应监听 focusin 并校验可聚焦项,关闭时通过 dataset 存储并验证触发源再回焦。

原生 <dialog></dialog> 元素不能自动维持焦点循环,必须手动补全初始聚焦、Tab 折返和关闭回焦三步,否则键盘用户一按 Tab 就逃逸到地址栏。
dialog.showModal() 后为什么焦点没进弹窗
因为 showModal() 只在打开瞬间尝试聚焦第一个可聚焦子元素,不保证该元素存在或已渲染。常见失败场景包括:弹窗内容由 JS 异步插入、首个子元素是 <div tabindex="-1">、或内部元素被 <code>display: none 隐藏。
- 必须在
showModal()后用requestAnimationFrame()延迟执行聚焦,确保 DOM 已就绪 - 优先查找
button、input、select、textarea或[tabindex="0"],跳过[tabindex="-1"] - 查不到时 fallback 到
dialog自身并设tabindex="-1",再调用.focus()
如何实现安全的 Tab 循环而不破坏屏幕阅读器
监听 keydown 拦 Tab 是错的——NVDA 的 Insert+F7、VoiceOver 的方向键都会失效。正确做法是监听 focusin,只在焦点即将离开容器时干预。
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
- 预收集有效可聚焦项:
Array.from(dialog.querySelectorAll('button:not([disabled]), input:not([disabled]), [tabindex="0"]')) - 过滤掉不可见元素:
el.offsetParent !== null && getComputedStyle(el).visibility !== 'hidden' - 在
focusin回调中判断:if (event.target === last && !event.shiftKey) { first.focus(); event.preventDefault(); } - 务必在
dialog.close()前移除该监听器,避免多个弹窗叠加时互相干扰
关闭 dialog 时焦点回不到触发按钮的根源
不是没存触发源,而是存的方式让 .focus() 静默失败:闭包里直接引用的 DOM 节点可能已被移除,或新路由下节点已不存在。
- 推荐把触发源 ID 存在 dialog 上:
dialog.dataset.triggerId = triggerBtn.id - 关闭前检查元素是否仍在文档流:
const el = document.getElementById(dialog.dataset.triggerId); if (el && el.offsetParent) el.focus(); - 别依赖
document.activeElement获取触发源——它在 close 瞬间常是body
真正难的不是写循环逻辑,而是每次 DOM 更新后重新校验可聚焦列表;异步内容、条件渲染、CSS 隐藏状态都会让预收集的结果失效,必须配合 dialog:open 事件或 MutationObserver 主动刷新。










