dialog 的 open 属性无法触发 CSS 过渡,需用 class 控制动画:默认 opacity:0、scale(0.95)、pointer-events:none;is-open 类启用动画;::backdrop 需显式声明 background 初始值;Safari 和 Firefox 存在兼容性问题;关闭时须监听 transitionend 再调用 close();焦点管理需手动补全。

dialog 的 open 属性不能触发 CSS 过渡
直接给 dialog 写 transition: opacity 0.3s,再用 dialog.showModal() 或设置 open 属性,动画不会执行——因为 open 是布尔属性,DOM 状态跳变,浏览器不生成中间帧。你看到的是“啪”一下出现或消失。
必须解耦语义与动效:open 仅保留可访问性支持(如屏幕阅读器识别),动画由 class 控制。
- 默认状态:设
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 的透明度过渡必须显式声明初始 background
dialog::backdrop 是伪元素,不参与常规渲染流,但支持 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的点击事件支持极弱,别依赖e.target === dialog判断 backdrop 点击(Chrome/Edge 可靠) - Firefox 当前完全不渲染
::backdrop(仅逻辑阻塞),需降级 fallback,例如加一层手动<div class="backdrop">
关闭时序错位会导致视觉撕裂
监听到 Esc 或 backdrop 点击后立刻调用 dialog.close(),::backdrop 会立即消失,而 dialog 主体还在退场动画中,造成“遮罩先没了,弹窗还悬在半空”的撕裂感。
必须严格按顺序处理:
- 绑定
keydown监听 Escape 键,触发dialog.classList.remove('is-open') - 绑定
dialog.addEventListener('transitionend', () => { if (!dialog.open) dialog.close(); }),确保 DOM 状态同步 - 若用 backdrop 点击关闭,推荐
dialog.addEventListener('click', e => { if (e.target === dialog) dialog.classList.remove('is-open'); })(注意 Safari 不可靠)
焦点管理是键盘可访问性的硬性要求
原生 dialog 调用 showModal() 会自动 trap focus,但加了 is-open 动画后,这个行为会被打断——尤其在 classList.add 和 showModal() 之间有微小延迟时。
必须手动补全:
- 打开后,立即
dialog.querySelector('[autofocus]')?.focus() || dialog.focus() - 关闭前,记录上一个聚焦元素(如
const prev = document.activeElement),关闭后prev?.focus() - 不要省略
tabindex="-1"在dialog上——否则部分旧版 Safari 无法聚焦
淡入淡出本身只是视觉层,但 open、focus、transitionend 三者的时间差和兼容性差异,才是实际项目里最常卡住的地方。


















