原生 默认支持 esc 关闭,但仅限 showmodal() 激活且内部有可聚焦元素时生效;需手动管理焦点循环、firefox 中需微任务延迟聚焦,并避免依赖只读 open 属性判断状态。

dialog 元素默认不支持 Esc 关闭?得手动监听
原生 <dialog></dialog> 在 Chrome/Firefox 中确实支持 Esc 关闭,但仅限于 showModal() 激活且焦点在 dialog 内部时才生效;如果弹窗内没有可聚焦元素(比如全是 <div>),按 Esc 就没反应。这不是 bug,是规范行为——浏览器只对“有焦点上下文”的模态框响应 Esc。
<p>实操建议:</p>
<ul>
<li>弹出后立即调用 <code>dialogElement.focus(),确保 dialog 本身获得初始焦点
tabindex="0" 的元素(如按钮或空 <div tabindex="0"></div>),否则焦点无法进入keydown:当 event.key === 'Escape' 且 event.target.closest('dialog') === dialogElement 时调用 dialogElement.close()
键盘 Tab 键无法在 dialog 内循环?需要手动管理焦点流
原生 <dialog></dialog> 不自动限制 Tab 键焦点范围,用户按 Tab 可能跳出弹窗,甚至回到地址栏。这是最常被忽略的可访问性问题。
实操建议:
- 监听
dialogElement.addEventListener('keydown', handleTab),在handleTab中拦截 Tab 键 - 用
dialogElement.querySelectorAll('[tabindex], button, input, select, textarea, a[href]')获取所有可聚焦子元素 - 当焦点在最后一个可聚焦项且用户按 Shift+Tab 时,将焦点设回第一个;反之亦然
- 别忘了处理
focusin事件:若焦点意外移出 dialog,立刻element.focus()强制拉回
Firefox 不触发 show() 后的 focus?得加微任务延迟
Firefox 对 dialog.show()(非模态)后的 .focus() 支持不稳定,同步调用常失败,控制台无报错,但焦点没进去。
实操建议:
- 改用
dialog.show()+setTimeout(() => dialog.focus(), 0)或queueMicrotask(() => dialog.focus()) - 更稳妥的做法是监听
dialog的aftertoggle事件(仅在状态切换完成时触发),再执行焦点操作 - 注意:只有
showModal()才会触发aftertoggle;show()不触发,必须用其他方式判断渲染就绪(如requestAnimationFrame)
dialog.open 属性不可靠?别直接读它判断状态
dialog.open 是只读属性,但它的值更新有延迟:调用 close() 后立即读取,可能还是 true;同理,show() 后也可能为 false。依赖它做条件判断容易出错。
实操建议:
- 用事件代替轮询:
dialog.addEventListener('close', handler)和dialog.addEventListener('cancel', handler)分别捕获关闭与 Esc 触发的关闭 - 维护一个外部布尔变量(如
isDialogOpen),在show()/showModal()后设为true,在close()和事件回调中设为false - 避免在
close事件回调里再次调用dialog.close(),这会触发重复事件,某些浏览器下导致崩溃











