dialog 的 open 属性是“激活态”触发器,设为 true 或空字符串才激活原生模态行为(提升层级、锁定焦点、拦截外部交互),移除该属性才真正关闭;仅 CSS 隐藏无法解除焦点锁定。
open 属性不是布尔开关,而是“激活态”触发器它不等价于 display: block 或 visibility: visible。设置 open 属性(哪怕设为 open="" 或 open="true")才会真正激活原生模态行为:浏览器自动提升层级、锁定焦点、拦截外部点击/Tab 键跳出。移除该属性(el.removeAttribute('open') 或 el.open = false)才真正关闭——仅靠 CSS 隐藏不会解除焦点锁定或 backdrop 遮罩。
常见错误:用 dialog.style.display = 'none' 隐藏后,用户仍无法用 Tab 切换到页面其他元素,控制台也无报错,但交互已卡死。
dialog.open = true 和 dialog.setAttribute('open', '') 效果一致,推荐前者(更语义、可读性好)dialog.open = false 会立即移除 open 属性并退出模态态;而 dialog.removeAttribute('open') 同样有效,但需确保属性确实存在focus(),浏览器会在 open 设置后自动将焦点移到 dialog 内第一个可聚焦元素(或自身,若无子元素可聚焦)open 状态,且默认不包含 body 或 html
当 dialog 处于 open 状态时,浏览器原生实现焦点围栏(focus trap):Tab/Shift+Tab 只在 dialog 内部循环,Esc 关闭,点击 backdrop 关闭。但这个围栏**不自动排除 body 上的事件监听器**——如果你在 body 上绑了 keydown 监听,它仍会触发(比如全局快捷键),可能干扰模态框行为。
Escape 以外的键(如 Enter 触发表单提交)dialog 不接管 document.activeElement 的初始值;如果打开前焦点在 input 上,关闭后焦点不会自动回退——需自行保存并恢复dialog 内无任何可聚焦元素(比如全是 div),焦点会落在 dialog 自身,此时按 Tab 会直接跳出——这是浏览器默认 fallback 行为,不是 bugdialog 在 open 状态下会被浏览器插入一个高优先级的伪 backdrop(::backdrop),其 z-index 是 UA 样式设定的(Chrome 中约为 2147483647),远高于常规 CSS 值。试图用 z-index: 9999 给父容器强行提层,大概率失败,还可能导致 backdrop 不显示或点击穿透。
dialog::backdrop,可用于调整遮罩背景色、动画,但不能改 z-indexdialog 并存时极易冲突——不要混用dialog 的 backdrop 支持较弱(iOS 16.4+ 才完整),部分机型点击 backdrop 不触发 close 事件,需额外监听 click 事件判断是否点在 backdrop 区域关闭 dialog 后,浏览器不会自动把焦点还原到打开前的元素。用户从键盘操作进入模态框,关闭后焦点丢失(停留在 body),对屏幕阅读器和键盘用户极不友好。
dialog.showModal() 前记录 document.activeElement,关闭后调用 .focus() 回去main 或 h1)dialog.close() 不触发 focusout,但会触发 close 事件,这是唯一可靠的关闭钩子真正麻烦的不是怎么打开,而是怎么让它关得干净、焦点不丢、不和现有逻辑打架。原生 dialog 的约束比想象中多,尤其在老项目里叠加使用时,细节比语法更关键。