display 不能参与 transition,因其为离散属性且无中间状态;应改用 opacity 与 visibility 协同控制,并配合 transitionend 事件、step-end 时机及 JS 清理确保体验。

transition 对 display 完全无效,不是写错了,是浏览器压根不处理——你写的 transition: display 0.3s 会被静默丢弃,DevTools 的 computed 面板里都找不到这条规则。
为什么 display 不能参与 transition
display 是离散属性,取值只有 none、block、flex 等枚举值,没有“半显示”这种中间状态。一旦设成 display: none,元素立刻从渲染树中移除,所有后续动画逻辑直接终止。这不是 bug,是 CSS 动画模型的设计限制:只有可插值(interpolatable)的数值型属性才能参与过渡。
用 opacity + visibility 替代的实操写法
这不是简单换两个属性,而是分工明确、时机对齐的协同控制:
-
opacity负责视觉渐变(0 → 1),必须声明在transition中 -
visibility负责交互与占位控制(hidden ↔ visible),也必须显式声明进transition,否则会瞬切 - 初始态必须同时设
opacity: 0和visibility: hidden,不能只靠opacity隐藏——否则元素仍挡点击、抢焦点 - 推荐写法:
.el { opacity: 0; visibility: hidden; transition: opacity 0.25s ease, visibility 0s 0.25s; },其中visibility 0s 0.25s表示延迟生效,卡在opacity动画结束那一刻
容易踩的坑:delay、step-end、JS 清理缺一不可
看似只是加个 visibility,但真实项目里翻车最多:
立即学习“前端免费学习笔记(深入)”;
- 快速连续开关时出现“闪一下又消失”:因为
visibility: hidden提前应用。解决方法是监听transitionend并过滤event.propertyName === 'opacity',或临时加pointer-events: none - 动画结束后没释放布局空间:若需像
display: none那样真正卸载,得在transitionend回调里补上el.style.display = 'none'和el.setAttribute('aria-hidden', 'true') - 父容器有
overflow: hidden且子元素占位,会导致滚动条抖动:此时仅靠visibility不够,必须 JS 清理 -
step-end很关键:它让visibility严格卡在最后一帧切换,避免opacity还没到 1 就突然可点;但别滥用在所有场景——它会让visibility变化显得生硬,适合模态框类控件
真正难的不是写出过渡,而是判断要不要保留占位、是否需要释放 DOM 空间、键盘焦点如何管——这些决策点,往往比 CSS 规则本身更影响最终体验。


















