原生 默认无动画,需手动用 transform 和 opacity 配合 [open] 伪类实现过渡;关闭时须 js 阻塞 close() 等待动画结束;safari 不支持 ::backdrop 动画,建议自定义遮罩层。

dialog 默认不带动画,transition 必须手动加在 open 状态上
<dialog></dialog> 自身没有内置过渡逻辑,showModal() 一调用就立刻显示,不会渐变或滑入。CSS 的 transition 不会自动作用于 display: none → block 这类切换,而 <dialog></dialog> 的显隐正是靠 display 控制的(open 属性为 true 时 display 变为 flex)。所以必须绕过 display,改用 opacity、transform 等可过渡属性,并配合 :modal 或 [open] 伪类来触发。
常见错误是直接写:dialog { transition: opacity 0.3s; } —— 没用,因为 open 前它压根不在渲染树里,transition 不会起效。
- 正确做法:给
dialog[open]设置初始透明度和位移,再用transition定义进入动画 - 必须同时设置
dialog:not([open])的隐藏态(如opacity: 0; pointer-events: none;),否则未 open 时仍可能被聚焦或拦截点击 - 避免对
dialog::backdrop直接加 transition:Safari 不支持 backdrop 的 CSS 动画,且 Chrome 中 backdrop 的 opacity 过渡在 showModal() 同步调用时容易卡顿
如何用 transform + opacity 实现弹入效果
最稳妥的过渡组合是 transform: scale() 和 opacity,它们都属于合成层属性,性能好,兼容性也覆盖到 Safari 15.4+。
示例代码:
dialog {
border: none;
padding: 0;
margin: 0;
/* 初始隐藏态 */
opacity: 0;
transform: scale(0.95);
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
dialog[open] {
opacity: 1;
transform: scale(1);
}
dialog:not([open]) {
pointer-events: none;
}
- scale(0.95) 比 scale(0) 更安全:避免 Safari 中因 transform 为 0 导致 focus() 失败或内容不可见
- 不要用
visibility: hidden替代pointer-events: none,否则键盘 tab 仍可能聚焦到 dialog 内部元素 - 如果 dialog 内有表单,过渡期间需确保第一个可聚焦元素(如 input)在 open 后能正确获得焦点——建议在
showModal()后加setTimeout(() => dialog.focus(), 0)
关闭时的过渡不能只靠 CSS,JS 需配合阻塞 close()
用户点「关闭」或按 Esc 触发 close(),dialog 会立刻从 DOM 中移除 open 状态,CSS 过渡来不及播放。所以不能依赖 [open] 的消失来触发动画,得反向操作:先加退出类,等动画结束再真正 close。
- 监听
dialog.addEventListener('close', () => {...}),在里面先dialog.classList.add('closing') - 定义
dialog.closing的过渡样式(比如opacity: 0; transform: scale(0.98);) - 用
getComputedStyle(dialog).opacity或setTimeout(..., 250)等过渡完成,再调用dialog.close()(注意:此时 open 已为 false,close 是幂等的) - 别用
animationend:dialog 元素本身不触发该事件;transitionend只在属性变化时触发,而close()不会触发 transitionend,必须手动控制
Safari 的 backdrop 动画缺失是硬伤,别强求
Chrome/Edge 支持 dialog::backdrop { transition: opacity 0.3s; },但 Safari 至今(Safari 17.6)不渲染 ::backdrop 的任何 transition,设了也白设。更麻烦的是,Safari 中 backdrop 点击无事件、无法检测,也就没法做“点击遮罩淡出”这种交互。
如果你需要全平台一致的遮罩过渡,唯一可靠方案是放弃原生 backdrop,自己用一个 <div class="backdrop"> 手动管理显隐和动画:<ul>
<li>showModal() 前插入 backdrop 元素并 <code>backdrop.classList.add('showing')
transitionend,再移除 backdrop 和 dialog原生 <dialog></dialog> 的价值在于语义、焦点锁定和基础模态行为,不是动画能力。把过渡当装饰,把 JS 控制当主线,才能真正落地。











