如何实现react-beautiful-dnd拖拽后重渲染组件的过渡动画?
我之前在项目里也碰到过完全一样的问题——react-beautiful-dnd自带的过渡只覆盖拖拽过程,当拖拽结束后元素因为业务逻辑(比如你说的直接跳到目标列表首位)触发重渲染时,确实不会自动带动画。不过有几个经过实践验证的方案可以解决这个问题,分享给你:
方案1:结合状态控制的CSS过渡动画
核心思路是在拖拽结束后,给目标元素添加临时的动画状态,通过CSS过渡实现位置变化的平滑效果,动画结束后再移除状态。
具体步骤:
- 给每个可拖拽元素维护一个
isAnimating状态,初始为false - 在
onDragEnd回调中,判断需要调整到首位的场景,更新列表数据的同时,标记该元素进入动画状态 - 利用CSS的
transition属性定义过渡效果,通过状态切换触发动画 - 监听动画结束事件,移除动画状态,避免影响后续交互
代码示例:
import { useState } from 'react'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; const ListItem = ({ item, isAnimating, onAnimationEnd }) => { return ( <div className={`list-item ${isAnimating ? 'slide-to-top' : ''}`} onAnimationEnd={onAnimationEnd} > {item.content} </div> ); }; const DragListDemo = () => { const [lists, setLists] = useState({ source: [{ id: '1', content: '源列表项1' }, { id: '2', content: '源列表项2' }], target: [{ id: '3', content: '目标列表项1' }, { id: '4', content: '目标列表项2' }] }); const [animatingItemId, setAnimatingItemId] = useState(null); const onDragEnd = (result) => { if (!result.destination) return; const { source, destination, draggableId } = result; // 处理跨列表拖拽并插入到目标列表首位的逻辑 if (source.droppableId !== destination.droppableId) { const sourceList = lists[source.droppableId]; const draggedItem = sourceList.find(item => item.id === draggableId); // 更新列表数据 setLists({ ...lists, [source.droppableId]: sourceList.filter(item => item.id !== draggableId), [destination.droppableId]: [draggedItem, ...lists[destination.droppableId]] }); // 标记需要动画的元素 setAnimatingItemId(draggableId); } }; const handleAnimationEnd = () => { setAnimatingItemId(null); }; return ( <DragDropContext onDragEnd={onDragEnd}> <div style={{ display: 'flex', gap: '20px', padding: '20px' }}> {/* 源列表 */} <Droppable droppableId="source"> {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef} style={{ width: '200px', border: '1px dashed #ccc', padding: '10px' }} > {lists.source.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > <ListItem item={item} isAnimating={animatingItemId === item.id} onAnimationEnd={handleAnimationEnd} /> </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> {/* 目标列表 */} <Droppable droppableId="target"> {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef} style={{ width: '200px', border: '1px dashed #ccc', padding: '10px' }} > {lists.target.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > <ListItem item={item} isAnimating={animatingItemId === item.id} onAnimationEnd={handleAnimationEnd} /> </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </div> </DragDropContext> ); }; export default DragListDemo;
对应的CSS:
.list-item { padding: 10px; margin: 8px 0; background: #f5f5f5; border-radius: 4px; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .slide-to-top { transform: translateY(-100%); }
方案2:使用
react-transition-group处理列表重排序动画 如果需要更复杂的入场/退场动画,可以结合react-transition-group的TransitionGroup和CSSTransition组件,它能自动检测列表元素的变化并触发动画。
核心要点:
- 用
TransitionGroup包裹整个可拖拽列表 - 每个列表项用
CSSTransition包裹,设置唯一的key(必须用元素的唯一ID,不能用索引) - 定义
enter、enter-active、exit、exit-active等CSS类来控制动画
关键注意事项
- 确保每个
Draggable组件的key和draggableId都使用元素的唯一ID,不能用索引,否则React无法正确识别元素的位置变化 - 动画时长要和CSS过渡时长匹配,避免出现状态不同步的问题
- 如果是横向列表,把
translateY换成translateX即可适配方向
内容的提问来源于stack exchange,提问作者Norayr Ghukasyan
相关产品推荐
相关产品推荐

