transitionend 事件监听不完整、CSS transition 拆分声明、未及时 blur 输入框、iOS Safari 的 will-change 延迟是导致弹窗淡出“突然消失”的四大主因,需统一监听、合并过渡声明、关闭前失焦、精准控制 will-change。

transitionend 事件监听不完整导致动画中断
淡出时弹窗“突然消失”,不是动画没写,而是 JS 在 transitionend 触发前就执行了 visibility: hidden 或 display: none。浏览器一旦看到 display: none,立刻终止所有渲染流程,动画直接被砍断。
必须确保:只监听一次退出动画完成,并且仅在该事件回调中收尾。常见错误是给多个属性(比如 opacity 和 transform)分别绑监听,结果 opacity 先结束、JS 就执行隐藏,transform 还没走完。
- 统一用一个
transitionend监听器,监听整个.modal元素 - 检查
event.propertyName,只对opacity或transform中任一关键属性响应(推荐opacity,它最后完成) - 动画结束后再设
visibility: hidden,并移除.is-open类;不要碰display - 为防多次触发,监听后立即
element.removeEventListener('transitionend', handler)
opacity + transform 动画未共用同一 transition 声明
CSS 中如果把 opacity 和 transform 的 transition 拆成两行写,浏览器会按属性分组调度重绘,极易造成退场不同步——比如 opacity 已到 0,transform 还卡在中间,视觉上就是“内容先透明、再缩回去”,看起来像提前消失。
正确写法是合并声明,强制它们共享同一时间轴:
立即学习“前端免费学习笔记(深入)”;
.modal {
opacity: 1;
transform: scale(1);
transition: opacity 0.25s ease-out, transform 0.25s ease-out;
}
.modal.is-closing {
opacity: 0;
transform: scale(0.95);
}
- 不要写成两行
transition,哪怕参数完全一样 - duration 和 timing-function 必须严格一致,否则无法对齐
- 移动端 Safari 对
transform插值更敏感,建议下限用scale(0.01)而非scale(0)
modal 关闭时未 blur() 内部 input 导致焦点残留
某些浏览器(尤其是 Chrome 和 Safari)在模态框关闭瞬间若仍有 input 或 textarea 处于 focus 状态,会触发页面重排或延迟销毁逻辑,间接打断动画帧。用户感知就是“点了关闭,弹窗闪一下就没了”,实际是动画被焦点管理机制抢断。
- 在触发关闭前,手动调用
document.activeElement?.blur() - 或者遍历
.modal内所有可聚焦元素:modal.querySelectorAll('input, textarea, select, button').forEach(el => el.blur()) - 注意:blur() 不会抛错,即使元素已失焦,也建议无条件执行
iOS Safari 的 will-change 渲染延迟补丁
iOS Safari(特别是 15–16 版本)对 opacity 变化有约 1–2 帧的合成层准备延迟,退场时遮罩先变透明、弹窗还挂着,就会产生“弹窗悬停半秒后突兀消失”的错觉。这不是 bug,是 WebKit 的图层提升策略所致。
临时解法是在开始退场前一帧主动提示浏览器提升图层:
.modal.is-closing {
will-change: opacity, transform;
}
- 必须在添加
.is-closing类的同时设置will-change,不能写在初始样式里 - 动画结束后(
transitionend回调中)立刻移除will-change,否则滚动/交互会卡顿 - 这不是通用方案,仅针对 iOS Safari;其他浏览器无需加


















