如何为数组映射渲染的JSX元素添加过渡动画?
为列表元素添加交换/删除过渡动画
问题背景
已实现基于Redux的课程列表管理逻辑:
- 通过
swap函数可上下交换元素位置,数组自动更新并重新排序 - 通过
delete函数可删除指定元素,后续元素自动上移 - UI能即时响应状态变化,但缺乏过渡动画,用户无法直观感知操作效果,需补充:
- 删除时200-300ms的消失过渡动画
- 交换时的提示动画(如位置移动平滑过渡或高亮提示)
解决方案
1. 定义CSS动画样式
先在全局或组件样式中添加过渡动画的CSS规则:
/* 列表项基础样式,统一过渡属性 */ .lecture-item { position: relative; padding: 1rem; margin: 0.5rem 0; border: 1px solid #eee; border-radius: 4px; transition: all 0.3s ease; } /* 删除动画:逐渐缩小、透明化直至消失 */ .lecture-item.deleting { opacity: 0; transform: scale(0.95); height: 0; padding: 0; margin: 0; overflow: hidden; } /* 交换时的高亮提示动画 */ .lecture-item.swapping { animation: swapHighlight 0.5s ease-in-out; } @keyframes swapHighlight { 0% { background-color: #fff3cd; } 100% { background-color: transparent; } }
2. 修改组件逻辑与JSX代码
通过临时状态标记动画状态,结合Redux更新实现过渡效果:
import { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import type { RootState } from '../store'; const LectureList = () => { const lectures = useSelector((state: RootState) => state.course.lectures); const dispatch = useDispatch(); // 存储正在执行动画的元素ID及动画类型 const [animatingItems, setAnimatingItems] = useState<Record<string, 'swap' | 'delete'>>({}); const handleDelete = (lecture) => { // 先标记删除动画状态 setAnimatingItems(prev => ({ ...prev, [lecture.id]: 'delete' })); // 等待动画完成后再触发Redux删除操作 setTimeout(() => { dispatch(/* 此处传入你的删除action */); // 清除动画标记 setAnimatingItems(prev => { const newState = { ...prev }; delete newState[lecture.id]; return newState; }); }, 300); }; const handleSwap = (lecture, direction) => { // 标记交换动画状态 setAnimatingItems(prev => ({ ...prev, [lecture.id]: 'swap' })); dispatch(/* 此处传入你的交换action */); // 动画结束后清除标记 setTimeout(() => { setAnimatingItems(prev => { const newState = { ...prev }; delete newState[lecture.id]; return newState; }); }, 500); }; return ( <div className="lecture-list"> {lectures.map(lect => { const animationType = animatingItems[lect.id]; return ( <div key={lect.id} // 必须使用元素唯一稳定ID,禁止用position_index(位置变化会导致DOM重绘) className={`lecture-item ${animationType === 'delete' ? 'deleting' : ''} ${animationType === 'swap' ? 'swapping' : ''}`} > <h3>{lect.position_index}</h3> <p>{lect.title}</p> <button onClick={() => handleSwap(lect, "up")} disabled={lect.position_index === 1}>UP</button> <button onClick={() => handleSwap(lect, "down")} disabled={lect.position_index === lectures.length}>DOWN</button> <button onClick={() => handleDelete(lect)}>DELETE</button> </div> ); })} </div> ); }; export default LectureList;
关键注意事项
- key值选择:必须使用元素的唯一稳定ID(如
lect.id),不能用position_index,否则元素位置交换时React会重新创建DOM节点,导致动画失效 - 动画与状态同步:删除操作需等待动画完成后再触发Redux更新,避免DOM被提前移除;交换动画则先触发状态更新,同时通过临时状态标记动画
- 边界按钮禁用:当元素处于首位/末位时,禁用UP/DOWN按钮,避免无效操作
内容的提问来源于stack exchange,提问作者Dimitar
相关产品推荐
相关产品推荐

