最稳方案是 visibility 与 opacity 联动,因 display 为离散属性无法过渡;遮罩需 fixed 定位、100vw/vh 尺寸、高 z-index 及 pointer-events 配置,并过滤 transitionend 的 propertyName 仅监听 opacity。

直接用 visibility + opacity 联动是最稳的方案,display: none 根本不能过渡,别试了。
为什么 transition 对 display:none 无效
因为 display 是离散属性,浏览器没法算出 display: none → display: block 的中间状态,transition 直接跳变。遮罩层必须始终在渲染流里,才能插值动画。
- 常见错误:加了
transition: all 0.3s,但显隐靠display切换,结果“啪”一下就出现/消失 - 真正起作用的只有连续可插值的属性:
opacity、visibility、transform等 -
visibility控制是否占空间+是否响应事件;opacity控制透明度——两者语义清晰、行为可控
visibility 和 opacity 的过渡时间怎么配
必须分开设时长,否则会出“幽灵层”:visibility 太晚切回 hidden,元素不可见但还能点到,用户误操作。
- 推荐写法:
transition: visibility 0.1s, opacity 0.3s; - 显示时加类(如
.show):visibility: visible; opacity: 1; - 隐藏时移除该类,transition 自动反向执行
- 千万别把
visibility的过渡设成0.3s—— 它只负责“收尾”,不是动画主体
全屏遮罩必须用 fixed + 四边归零
position: absolute 挂在某个父容器里,滚动后遮罩错位、被 overflow: hidden 截断,是线上高频问题。
立即学习“前端免费学习笔记(深入)”;
- 必须写:
position: fixed; top: 0; right: 0; bottom: 0; left: 0; - 尺寸用
width: 100vw; height: 100vh;,比100%更可靠(避开 body padding 或缩放干扰) -
z-index至少设到9999,真实项目里被弹窗、广告位压过太多次 - 加
pointer-events: none;到遮罩本身,内部关闭按钮再设pointer-events: auto;
transitionend 事件监听要过滤 propertyName
不加过滤,visibility 和 opacity 各触发一次 transitionend,回调执行两遍,状态容易错乱。
- 只监听 opacity:
el.addEventListener('transitionend', (e) => { if (e.propertyName === 'opacity') { /* 隐藏后清理 */ } }); - 移除监听前记得调用
removeEventListener,否则内存泄漏 - 别在
visibility: hidden后立刻remove()元素——动画还没结束,transitionend就永远不会触发
最易被忽略的是 pointer-events 配置和 transitionend 的属性过滤。前者导致用户点不到背后的按钮,后者让回调执行两次,状态错乱。


















