因为li元素增删时浏览器无法计算height等属性的起止值,transition无值可插值;应使用opacity+transform组合,或max-height替代height,并注意class添加时机与SSR兼容性。

列表项添加过渡效果为什么不能直接用 transition 作用于 li?
因为 li 元素默认是不可见尺寸变化的——你增删 li 时,浏览器不会自动计算高度、宽度等几何属性的起止值,transition 就没东西可“过渡”。常见现象是:新 li 突然弹出,或旧 li 瞬间消失,完全没动画。
根本原因是:CSS 过渡只对「已知数值变化的属性」生效(比如 opacity、transform),而 height 从 0 到 auto 是无效组合,浏览器无法插值。
实操建议:
- 用
opacity+transform组合做入场/退场,最稳定、性能好、兼容性强 - 避免对
height、margin、padding做transition,尤其当值含auto时 - 若必须动高度,改用
max-height(设一个足够大的固定值,如max-height: 500px),并确保起始值为0
用 opacity 和 transform 实现平滑列表过渡
这是目前最推荐的方式。关键在于:给新增/待删的 li 添加 class 控制状态,并配合 transition 声明在父容器或 li 上。
立即学习“前端免费学习笔记(深入)”;
示例结构:
<ul class="list"> <li class="list-item is-entering">Item 1</li> <li class="list-item is-entering">Item 2</li> </ul>
CSS 配置:
.list-item {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.list-item.is-entering {
opacity: 1;
transform: translateY(0);
}
注意点:
- 过渡必须写在基础 class(如
.list-item)上,而不是状态 class 里,否则首次渲染无过渡 -
is-entering应通过 JS 在元素插入 DOM 后下一帧添加(例如用requestAnimationFrame或setTimeout(..., 0)),否则样式来不及生效 - 移除时同理:先加
is-leaving触发退出动画,监听transitionend后再真正removeChild
Vue/React 中列表过渡的底层逻辑其实是相同的
框架封装的 <transition-group> 或 <CSSTransition> 只是帮你自动管理 class 的增删时机,最终还是靠上面那套 CSS 规则驱动。一旦发现动画不触发,优先检查:
- 是否遗漏了基础 class 的
transition声明(比如只写了.list-item-enter却没写.list-item的过渡属性) - JS 是否在元素挂载前就加了状态 class(导致初始状态和目标状态相同,无变化)
- 父容器是否设置了
overflow: hidden却忘了适配transform引起的层叠上下文问题 - 使用了
display: none切换 —— 这会直接中断过渡,应改用visibility: hidden+opacity
服务端渲染或首屏直出时的过渡兼容性陷阱
服务端生成的 HTML 默认没有过渡 class,客户端 hydrate 后如果立即添加 is-entering,会导致首屏所有列表项“集体闪入”,破坏体验。
解决方案很实际:
- 服务端输出时,给每个
li加一个data-ssr="true"属性 - 客户端 JS 初始化后,遍历所有
[data-ssr]元素,用requestAnimationFrame批量移除该属性并添加is-entering - CSS 中用
li:not([data-ssr])来限定过渡规则,避免 SSR 内容一上来就动
这个细节容易被忽略,但直接影响首屏动画是否自然。过渡不是加个 transition 就完事,时机、上下文、初始化状态都得对得上。



















