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

如何为数组映射渲染的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 13:15:29