You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用React Transition Group实现列表项增删时高度平滑动画

React Transition Group 列表增删时容器高度平滑过渡实现方案

核心问题本质是CSS原生不支持height: auto到固定像素值的自动过渡,直接增删节点时浏览器会瞬间重排到内容固有高度,就会出现跳变。下面两种方案都能解决,第一种不需要额外引入依赖,完全适配现有React Transition Group的代码逻辑。


方案1:基于Transition生命周期手动控制容器高度(无额外依赖)

核心思路是在项入场、离场的动画节点,通过JS实时计算容器的真实内容高度,给容器设置可过渡的固定height值,动画结束后再还原为auto适配后续内容变化。

  • 实现步骤:
    • 给列表外层容器绑定ref,添加height属性的CSS过渡,设置overflow: hidden避免内容溢出穿帮
    • 监听CSSTransition的onEnter、onExit回调,每次项开始入场/离场时,先拿到容器当前的真实滚动高度,再动态设置容器的height值触发过渡
    • 动画结束后(监听onEntered、onExited回调)把容器height重置为auto,适配窗口缩放、内容动态变更的场景

代码示例:

import { useRef, useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

function AnimateList() {
  const [list, setList] = useState([]);
  const containerRef = useRef(null);

  // 计算并更新容器真实高度,触发过渡
  const updateContainerHeight = () => {
    const el = containerRef.current;
    if (!el) return;
    // 临时切到auto拿真实内容高度
    const prevHeight = el.style.height;
    el.style.height = 'auto';
    const targetHeight = `${el.scrollHeight}px`;
    // 还原之前的高度,强制重排后设置目标高度,保证过渡触发
    el.style.height = prevHeight;
    el.offsetHeight; // 读取offset属性强制浏览器重排,不能省
    el.style.height = targetHeight;
  };

  // 动画结束后重置高度为auto
  const resetHeight = () => {
    if (containerRef.current) containerRef.current.style.height = 'auto';
  };

  return (
    <div
      ref={containerRef}
      style={{
        transition: 'height 300ms ease-in-out',
        overflow: 'hidden'
      }}
    >
      <TransitionGroup>
        {list.map(item => (
          <CSSTransition
            key={item.id}
            timeout={300}
            classNames="list-item"
            onEnter={updateContainerHeight}
            onEntered={resetHeight}
            onExit={updateContainerHeight}
            onExited={resetHeight}
          >
            <div className="list-item-content">{item.text}</div>
          </CSSTransition>
        ))}
      </TransitionGroup>
    </div>
  );
}

配套的列表项动画CSS:

.list-item-enter {
  opacity: 0;
  transform: translateY(-8px);
}
.list-item-enter-active {
  opacity: 1;
  transform: translateY(0);
  transition: all 300ms ease-in-out;
}
.list-item-exit {
  opacity: 1;
}
.list-item-exit-active {
  opacity: 0;
  transform: translateY(-8px);
  transition: all 300ms ease-in-out;
}

方案2:配合现成折叠动画组件实现(少写DOM操作逻辑)

如果不想手动处理高度计算、重排触发这些细节,可以直接用成熟的折叠动画组件和React Transition Group搭配,组件内部会自动处理高度计算逻辑。
用法示例:

// 安装对应依赖后直接引入
import { Collapse } from 'react-collapse';

// 在CSSTransition内部包裹一层Collapse即可
<CSSTransition timeout={300} key={item.id}>
  <Collapse isOpened={true}>
    <div className="list-item-content">{item.text}</div>
  </Collapse>
</CSSTransition>

这种写法不需要手动给外层容器加高度控制逻辑,每一项的展开收起动画会自动撑出容器的平滑高度过渡。


常见踩坑

  • 不要尝试直接用CSS写height: auto的transition,所有现代浏览器都不支持auto值和长度值之间的插值过渡,必须通过JS桥接固定高度值才能生效
  • 列表项尽量不要用外边距(margin)做间距,margin折叠会导致scrollHeight计算偏差,用内边距(padding)代替更稳妥
  • 如果列表项内有图片、异步加载的内容,要等资源加载完成后再调用一次高度更新方法,否则会出现高度计算不足导致的二次跳变

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:27:17