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

React CSSTransition列表项动画不生效问题求助

解决React Transition Group列表项逐个入场动画不生效的问题

核心问题分析

原代码存在几个关键问题导致动画失效:

  1. TransitionGroup的嵌套结构错误,无法正确监听子元素的挂载状态
  2. 使用createRef存储元素引用的方式不可靠,组件重渲染时会丢失引用
  3. 错误地将动画延迟加到timeout属性上,而非设置CSS过渡延迟
  4. 冗余的key属性和不必要的CSS属性干扰动画逻辑

修复后的完整代码

组件代码

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

const CharList = (props) => {
    const [chars, setChars] = useState([]);
    // 用useRef数组存储每个列表项的引用,保证引用稳定
    const itemRefs = useRef([]);

    const onCharLoaded = async (newChars) => {
        // 不需要在char对象里存ref,直接用useRef数组管理
        setChars(prevChars => [...prevChars, ...newChars]);
    };

    // 发起API请求获取数据的逻辑
    useEffect(() => {
        const fetchChars = async () => {
            // 替换为实际API请求
            const mockChars = [
                { id: 1, name: 'Iron Man', thumbnail: 'https://example.com/ironman.jpg' },
                { id: 2, name: 'Captain America', thumbnail: 'https://example.com/cap.jpg' },
                { id: 3, name: 'Thor', thumbnail: 'https://example.com/thor.jpg' }
            ];
            await onCharLoaded(mockChars);
        };
        fetchChars();
    }, []);

    const focusOnItem = (ref) => {
        ref.current?.focus();
    };

    function renderItems(arr) {
        return (
            // TransitionGroup直接作为ul容器,component指定为ul
            <TransitionGroup component="ul" className="char__grid">
                {arr.map((item, i) => (
                    <CSSTransition
                        key={item.id}
                        // timeout = 动画时长 + 当前项的延迟时间,确保CSSTransition等待动画完成
                        timeout={300 + i * 100}
                        classNames="item"
                        // 使用回调ref存入useRef数组
                        nodeRef={itemRefs.current[i]}
                    >
                        <li
                            className="char__item"
                            tabIndex={0}
                            // 回调ref,将元素存入数组
                            ref={el => itemRefs.current[i] = el}
                            onClick={() => {
                                props.onCharSelected(item.id);
                                focusOnItem(itemRefs.current[i]);
                            }}
                            onKeyDown={(e) => {
                                if (e.key === " " || e.key === "Enter") {
                                    props.onCharSelected(item.id);
                                    focusOnItem(itemRefs.current[i]);
                                }
                            }}
                            // 给每个项设置过渡延迟,实现逐个出现的效果
                            style={{ transitionDelay: `${i * 100}ms` }}
                        >
                            <img src={item.thumbnail} alt={item.name} />
                            <div className="char__name">{item.name}</div>
                        </li>
                    </CSSTransition>
                ))}
            </TransitionGroup>
        );
    }

    return <>{renderItems(chars)}</>;
};

export default CharList;

CSS代码

/* 简化动画样式,去掉不必要的visibility属性 */
.item-enter {
    opacity: 0;
    transform: translateY(20px); /* 可选:添加位移效果增强动画 */
}

.item-enter-active {
    opacity: 1;
    transform: translateY(0);
    transition: all 300ms ease-out; /* 指定过渡曲线,动画更流畅 */
}

.item-exit {
    opacity: 1;
    transform: translateY(0);
}

.item-exit-active {
    opacity: 0;
    transform: translateY(20px);
    transition: all 300ms ease-out;
}

/* 基础列表样式 */
.char__grid {
    list-style: none;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 1rem;
}

.char__item {
    cursor: pointer;
    padding: 0.5rem;
    border-radius: 4px;
    text-align: center;
}

.char__item img {
    width: 100%;
    height: auto;
    border-radius: 4px;
}

关键修复点说明

  • TransitionGroup结构调整:将TransitionGroup设置为列表容器(通过component="ul"),确保它能直接监听子元素CSSTransition的挂载/卸载事件,这是动画生效的前提。
  • 引用管理优化:使用useRef([])数组存储列表项引用,替代原有的createRef,避免组件重渲染时丢失引用,确保nodeRef能正确绑定到DOM元素。
  • 动画延迟设置:通过style={{ transitionDelay: ${i * 100}ms }}给每个项设置递增的延迟,而不是修改timeout属性。timeout属性仅用于告知CSSTransition等待动画完成的总时长(动画时长+延迟)。
  • CSS简化:移除不必要的visibility属性,改用opacity和可选的transform实现平滑过渡,避免属性冲突。
  • 冗余key移除:仅在CSSTransition上设置唯一key,不需要在内部li元素重复设置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:54:51