dialog 的 open 属性无法触发 CSS 动画,需用 is-open 类控制过渡;::backdrop 需显式声明初始 background 值才能渐变;关闭时须等待 transitionend 再调用 close();焦点必须手动管理以确保键盘可访问性。

dialog 的 open 属性不能直接触发 CSS 动画
直接写 dialog { transition: opacity 0.3s; } 并设置 open 属性,动画不会生效——因为 open 是布尔属性,切换时 DOM 状态跳变,浏览器不生成中间帧。你看到的永远是“啪”一下出现或消失。
必须用 class 控制过渡状态:is-open 类负责启用动画和交互,open 属性只保留语义与可访问性支持(比如屏幕阅读器识别)。
- 初始样式设为
opacity: 0; transform: scale(0.95); pointer-events: none; -
is-open类中设为opacity: 1; transform: scale(1); pointer-events: auto; - 打开时:先
dialog.classList.add('is-open'),再调用dialog.showModal() - 关闭时:先
dialog.classList.remove('is-open'),等transitionend后再调用dialog.close()
::backdrop 的透明度过渡必须显式声明初始值
dialog::backdrop 是伪元素,不参与常规 DOM 流,但它支持 transition——前提是初始状态明确。如果只写 background: rgba(0,0,0,0.6),它会瞬间“啪”地出现,没有渐变。
正确写法是:
立即学习“前端免费学习笔记(深入)”;
dialog::backdrop {
background: rgba(0, 0, 0, 0);
transition: background 0.25s ease;
}
dialog.is-open::backdrop {
background: rgba(0, 0, 0, 0.6);
}
- 过渡时间建议控制在
0.25s–0.4s区间:太短人眼难感知,太长拖慢操作反馈 - Safari 对
::backdrop的事件支持极弱(比如点击无法可靠判断是否点在 backdrop 上),别把它当交互依据 - Firefox 当前完全不渲染
::backdrop(仅逻辑阻塞),需降级 fallback,例如加一层手动<div class="backdrop">
关闭时序错位会导致视觉撕裂
如果监听到点击或 Esc 后立刻调用 dialog.close(),::backdrop 会立即消失,而 dialog 主体还在退场动画中,造成“遮罩先没了,弹窗还悬在半空”的撕裂感。
关键在于解耦动画与 DOM 状态:
- 点击 backdrop 关闭?用
dialog.addEventListener('click', e => { if (e.target === dialog) { dialog.classList.remove('is-open'); } })(Chrome/Edge 可靠,Safari 不行) - 按 Esc 关闭?监听
keydown,捕获Escape键并同样触发classList.remove - 统一在
transitionend里调用dialog.close(),且只监听一次(用{ once: true }) - 务必监听
close事件做后续清理(如重置表单、恢复焦点),不要只依赖 click 或 keydown
焦点管理不到位会让键盘用户“掉出弹窗”
即使动画丝滑、遮罩渐变到位,如果没锁住焦点,Tab 键仍能切到背景按钮或输入框——这违反 WCAG,也破坏体验。
showModal() 会自动聚焦第一个可聚焦子元素,但有前提:
- dialog 内必须至少有一个可聚焦元素(如
<button>、<input>,或带tabindex="0"的容器) - 不能用
display: none或visibility: hidden隐藏内容,否则元素不可聚焦 - Safari 中首次打开可能失焦,需加
setTimeout(() => dialog.focus(), 0)强制 - 关闭后建议手动
document.body.focus(),避免焦点丢失在空白处
最常被忽略的是:动画完成 ≠ 焦点就绪。class 切换、showModal() 调用、focus 行为三者时机稍有错位,就会让键盘用户卡住。



















