列表增删无过渡动画的根本原因是DOM突变触发强制重排,CSS无法捕获中间状态;需用FLIP技术将布局变化转为transform/opacity等可动画属性,并严格把控读取、回滚、切换、播放四步时机。

列表增删没有过渡动画,不是你 CSS 写得不对,而是 DOM 突变本身不触发可动画的中间状态——display: none、appendChild、removeChild 这些操作一执行,浏览器直接重排,CSS 根本来不及插手。
为什么直接加 transition 没用
常见错误是给 .item 加 transition: opacity 0.3s, height 0.3s,然后在 JS 里直接 el.remove() 或 el.style.display = 'none'。这会导致:元素瞬间消失,动画根本没机会开始;或者高度从 auto 跳到 0,因为 height: auto 不可过渡。
-
height: auto不能被 transition,必须转成固定值或max-height -
display、visibility切换会跳过渲染流水线中的“动画帧”,浏览器不生成中间状态 - 哪怕用了
opacity,如果元素被移出 DOM,opacity动画也会立刻中断
FLIP 四步必须手动控制时机
FLIP 不是某个 API,而是一套靠 JS 主动干预渲染时序的模式。关键不在“怎么写 CSS”,而在“什么时候读、什么时候切、什么时候触发重绘”。漏掉任意一步,动画就卡住或跳变。
-
First:用
getBoundingClientRect()或getComputedStyle()同步读取所有待动画元素的top、left、height、opacity -
Last:立即执行增删操作(比如
list.appendChild(newItem)),让 DOM 变成目标状态 -
Invert:在同一个 JS 任务中,**立刻**用
offsetHeight或getBoundingClientRect()强制回流,再把每个元素用transform或opacity“拉回”视觉旧位置 -
Play:用
requestAnimationFrame切换到目标样式,比如el.style.transform = 'none',此时 transition 才真正生效
React/Vue 中 <transition-group> 为啥闪动或错位
框架封装的 <transition-group> 本质也是 FLIP,但它只处理 enter/leave,不自动处理 reordering(比如 filter 后顺序变化、拖拽排序)。闪动和错位几乎都源于 DOM 定位失控。
立即学习“前端免费学习笔记(深入)”;
-
key用数组索引(:key="index")——filter 后索引重排,key 失效,框架复用错元素 - 容器没设
position: relative,导致 leave 阶段元素被设为position: absolute后脱离文档流,挤占其他项空间 - 新插入元素初始状态没设
opacity: 0+transform: scaleY(0),而是等 DOM 插入后再设,错过 FLIP 的 Invert 时机
最简可用的原生 FLIP 列表增删示例
不依赖框架,仅需 20 行核心 JS。重点看 offsetHeight 强制回流和 requestAnimationFrame 的配合:
function addItem(list, newItem) {
const before = Array.from(list.children).map(el => ({
id: el.dataset.id,
rect: el.getBoundingClientRect()
}));
list.appendChild(newItem); // Last:立即插入
newItem.style.opacity = '0';
newItem.style.transform = 'scaleY(0)';
newItem.offsetHeight; // Invert:强制回流,让上面两行生效
requestAnimationFrame(() => { // Play
newItem.style.transition = 'opacity 0.25s, transform 0.25s';
newItem.style.opacity = '1';
newItem.style.transform = 'scaleY(1)';
});
}
真正难的不是写这几行代码,而是理解:浏览器渲染是分帧的,JS 是单线程的,你必须在“同一帧内完成读-写-强制回流”,才能骗过浏览器让它以为“布局没变”,只动画视觉属性。这个时机稍一错,就是白屏半秒或元素乱飞。



















