应使用 opacity + max-height 过渡配合 overflow: hidden 和 transitionend 监听实现自然收起再删除:先加 .is-collapsing 类触发动画,待 max-height 过渡结束再 remove()。

transition 透明度 + 高度变化为什么删不干净
直接对 height 用 transition 做“收缩消失”,几乎一定会卡住或闪一下——因为 height: 0 时,如果内容有 padding/margin/border,或者子元素触发了最小高度(比如文字撑开的行高),浏览器实际渲染的高度往往不是 0,导致动画停在某个非零值,卡片“悬在半空”。更麻烦的是,height 无法从 auto 过渡到具体数值,你写 height: auto → height: 0,浏览器直接跳变。
- 别用
height: auto做起始值;改用固定高度(如height: fit-content不可靠,尤其在 Flex/Grid 容器里) - 真正要过渡的,是
opacity和max-height(而非height),配合overflow: hidden - 给
max-height设一个“足够大但不过分”的上限值(比如max-height: 500px),避免动画过长;太小会截断内容,太大动画慢 - 删除前必须先触发动画,再等动画结束才真正
remove()或设display: none,否则动画被中断
怎么写才能让卡片“自然收起再消失”
核心是两阶段:先视觉收起(opacity + max-height),再 DOM 移除。不能靠 CSS 自己“删掉自己”,得 JS 配合监听 transitionend。
- CSS 中定义:初始状态
max-height: 500px、opacity: 1;删除态加类如.is-collapsing,设max-height: 0、opacity: 0、overflow: hidden、transition: opacity 0.2s ease, max-height 0.3s ease - JS 点击后,先
element.classList.add('is-collapsing'),再监听transitionend事件,检查event.propertyName是否为'max-height'(避免 opacity 结束就删) - 监听回调里调用
element.remove(),或element.style.display = 'none'(如果还要复用) - 注意:如果卡片内含图片、iframe 或异步加载内容,
max-height上限要预留足够空间,否则收起过程会被裁切
为什么不用 transform + opacity 组合
单纯用 transform: scale(0.95) translateZ(0) + opacity 是最顺滑的方案,但它只是“看起来消失了”,卡片仍占文档流位置,且没有“高度收缩”的视觉反馈。如果你要的确实是“像抽屉一样缩进去”,就必须动尺寸相关属性;而 transform 不影响布局,做不到这点。
-
transform动画性能好,但无法替代max-height的布局收缩效果 - 若同时用
transform和max-height,需注意transform会创建新层叠上下文,可能影响 z-index 或遮罩逻辑 - 移动端 Safari 对
max-height过渡偶尔有渲染延迟,可加will-change: max-height(但别滥用,会内存开销)
删除动画结束后 DOM 还在,怎么办
这是最常被忽略的一环:动画只是视觉效果,DOM 节点默认不会自动销毁。用户点了删除,动画播完了,但节点还在内存里,甚至还能被 JS 查到、被屏幕阅读器读到。
立即学习 “ 前端免费学习笔记(深入)”;
- 必须在
transitionend后明确执行element.remove()或element.parentElement.removeChild(element) - 如果用了 Vue/React,别只靠
v-if或key变更——它们内部也是靠类似机制,但你要确保过渡类名和生命周期钩子对得上(比如 Vue 的@after-leave) - 测试时用 DevTools 的 Elements 面板观察节点是否真被移除,而不是只看是否“看不见”