React Transition Group:如何实现Todo列表元素上移动画?
解决React Transition Group删除Todo后后续元素上移的过渡动画问题
你现在的代码只实现了删除元素的淡出,但后续元素是直接跳上去的——这是因为被删元素一移除,浏览器就立刻重排了布局,没给位置变化留过渡时间。要做平滑上移,得让被删元素的高度慢慢坍缩,同时让父容器的高度也跟着过渡,这样后面的元素就会跟着滑上去。
修改步骤
1. 重写CSS样式
给列表项加全属性过渡,同时定义退出时的坍缩状态:
li { list-style: none; margin: 8px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; /* 所有可能变化的属性都加过渡,和组件里的timeout时间对齐 */ transition: all 700ms ease-out; } /* 退出前的初始状态 */ .item-exit { opacity: 1; height: auto; margin: 8px 0; } /* 退出过程中的活跃状态:高度坍缩+淡出 */ .item-exit-active { opacity: 0; height: 0; margin: 0; padding: 0; overflow: hidden; /* 避免文字在高度缩小的时候露出来 */ }
2. 调整组件代码
重点是给TransitionGroup容器加过渡,同时把列表项的key换成唯一id(用内容当key遇到重复Todo会炸,必须改):
import { useState } from 'react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import './styles.css'; // 改成带唯一id的数组,避免key冲突 const initial = [ { id: 1, text: 'Walk dog' }, { id: 2, text: 'Sweep floors' }, { id: 3, text: 'Do homework' } ]; export default function App() { const [todos, setTodos] = useState(initial); return ( <TransitionGroup component="ul" style={{ padding: 0, transition: 'all 700ms ease-out' }} > {todos.map((todo) => ( <CSSTransition key={todo.id} timeout={700} classNames="item"> <li> {todo.text}{' '} <button onClick={() => { setTodos(todos.filter((t) => t.id !== todo.id)); }} > Delete </button> </li> </CSSTransition> ))} </TransitionGroup> ); }
核心逻辑说明
- 高度坍缩:被删元素在退出时,height从auto降到0,同时margin和padding也归零,这样元素是慢慢“缩没”的,不是直接消失。
- 容器过渡:ul容器也加了过渡,它的高度会随着内部元素的坍缩慢慢变化,后面的列表项就会跟着平滑上移。
- 唯一key:用id当key是React列表的基本要求,不然遇到重复内容,React会搞混组件实例,动画和渲染都会出问题。
- overflow:hidden:防止列表项缩小时,里面的文字或者按钮露出来,保证动画干净。
改完之后,删除Todo时,被删项会先淡出+缩成一条线,后面的项跟着缓缓上移,就是你想要的平滑效果了。
内容的提问来源于stack exchange,提问作者DariusW
相关产品推荐
相关产品推荐

