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

React Transition Group CSS过渡异常问题排查与修复求助

组件切换CSS过渡问题排查与修复

问题描述

使用React Transition Group的SwitchTransition和CSSTransition实现组件切换时遇到三个问题:

  • 过渡方向不符:实际为从右到左,预期为从左到右
  • 过渡时机错误:旧组件直接被替换后新组件才开始过渡,预期新旧组件同时进行过渡
  • 交换.fadeEnterActive与.fadeExitActive类后,第二个问题解决,但进入元素初始就带有.fadeEnterDone类名

现有代码

React组件代码

import { useRef, useState } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import PayContainer from "../payContainer";
import PersonalInfoForm from "../persnalInfoForm";
import styles from './drawer_container.module.scss'

export default function DrawerContainer() {
    const [state, setState] = useState('pay-fill');
    const payRef = useRef(null);
    const personalInfoRef = useRef(null);
    const previewRef = useRef(null);
    const nodeRef = () => {
        switch (state) {
            case 'pay-fill':
                return payRef
            case 'personalinfo-fill':
                return personalInfoRef
            case 'preview':
                return previewRef
            default:
                return null;
        }
    }

    const setActiveState = (curState: string) => {
        setState(curState)
    }

    const getWidget = () => {
        switch (state) {
            case 'pay-fill':
                return <PayContainer setState={setActiveState}/>
            case 'personalinfo-fill':
                return <PersonalInfoForm />
            case 'preview':
                    return <div>preview nigga</div>
            default:
                return <div>default</div>;
        }
    }
    return <div className="w-full h-full">
    <SwitchTransition mode={'out-in'}>
      <CSSTransition
        key={state}
        nodeRef={nodeRef()}
        addEndListener={(done: () => void) => {
          nodeRef()?.current?.addEventListener("transitionend", done, false);
        }}
        timeout={500}
        classNames={{
            enterActive: styles.fadeEnterActive,
            enterDone: styles.fadeEnterDone,
            exitActive: styles.fadeExitActive,
            exitDone: styles.fadeExitDone
          }}
      >
        <div ref={nodeRef()}  className={['w-full h-full', styles.fade].join(' ')}>
          {getWidget()}
        </div>
      </CSSTransition>
    </SwitchTransition>
  </div>;
}

SCSS样式代码

.fadeEnterActive {
  opacity: 0;
  transform: translateX(-100%);
}
.fadeEnterDone {
  opacity: 1;
  transform: translateX(0%);
}
.fadeExitActive {
  opacity: 1;
  transform: translateX(0%);
}
.fadeExitDone {
  opacity: 0;
  transform: translateX(100%);
}
.fadeEnterActive,
.fadeExitActive {
  transition: opacity 500ms, transform 500ms;
}

.fade {
  
}

问题核心分析

  1. 多Ref绑定错误:为每个状态单独创建ref,导致CSSTransition无法跟踪旧元素的退出状态,直接卸载旧元素后才渲染新元素,引发过渡时机错误。
  2. CSS类逻辑缺失:未定义enter和exit初始状态类,仅用enterActive作为进入起始状态,导致过渡方向逻辑混乱,且无法正确触发状态切换。
  3. 切换模式不匹配:SwitchTransition的out-in模式是旧元素完全退出后新元素才进入,与“同时过渡”的需求不符。

完整修复方案

修复后的React组件代码

import { useRef, useState } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import PayContainer from "../payContainer";
import PersonalInfoForm from "../persnalInfoForm";
import styles from './drawer_container.module.scss'

export default function DrawerContainer() {
    const [state, setState] = useState('pay-fill');
    // 统一使用单个ref绑定切换容器
    const containerRef = useRef(null);

    const setActiveState = (curState: string) => {
        setState(curState)
    }

    const getWidget = () => {
        switch (state) {
            case 'pay-fill':
                return <PayContainer setState={setActiveState}/>
            case 'personalinfo-fill':
                return <PersonalInfoForm />
            case 'preview':
                    return <div>preview nigga</div>
            default:
                return <div>default</div>;
        }
    }

    return (
        <div className={`w-full h-full relative ${styles.switchContainer}`}>
            <SwitchTransition mode="in-out">
                <CSSTransition
                    key={state}
                    nodeRef={containerRef}
                    timeout={500}
                    classNames={{
                        enter: styles.fadeEnter,
                        enterActive: styles.fadeEnterActive,
                        enterDone: styles.fadeEnterDone,
                        exit: styles.fadeExit,
                        exitActive: styles.fadeExitActive,
                        exitDone: styles.fadeExitDone
                    }}
                >
                    <div ref={containerRef} className={`w-full h-full absolute top-0 left-0 ${styles.fade}`}>
                        {getWidget()}
                    </div>
                </CSSTransition>
            </SwitchTransition>
        </div>
    );
}

修复后的SCSS样式代码

.switchContainer {
    overflow: hidden; // 隐藏容器外的过渡元素
}

.fade {
    position: absolute;
    top: 0;
    left: 0;
}

// 进入动画初始状态:从左侧容器外进入
.fadeEnter {
    opacity: 0;
    transform: translateX(-100%);
}

.fadeEnterActive {
    opacity: 1;
    transform: translateX(0);
    transition: opacity 500ms, transform 500ms ease-in-out;
}

.fadeEnterDone {
    opacity: 1;
    transform: translateX(0);
}

// 退出动画初始状态:停留在当前位置
.fadeExit {
    opacity: 1;
    transform: translateX(0);
}

.fadeExitActive {
    opacity: 0;
    transform: translateX(100%); // 向右移出容器
    transition: opacity 500ms, transform 500ms ease-in-out;
}

.fadeExitDone {
    opacity: 0;
    transform: translateX(100%);
}

修复说明

  • 统一Ref绑定:移除多ref设置,改用单个containerRef绑定切换容器,确保CSSTransition能正确监听元素的进入/退出状态,解决旧组件直接被替换、新元素初始带enterDone类的问题。
  • 修正CSS过渡逻辑:补充fadeEnter和fadeExit初始状态类,明确过渡的起始/结束状态,将进入动画初始位置设为translateX(-100%)(左侧外),退出动画结束位置设为translateX(100%)(右侧外),完美匹配从左到右的预期过渡方向。
  • 布局优化:容器设置relative,切换元素设置absolute实现新旧元素重叠,配合容器overflow: hidden避免过渡元素超出可视范围,达到同时过渡的视觉效果。
  • 切换模式调整:SwitchTransition使用mode="in-out",让新旧组件同时触发过渡动画;若需要先退出再进入的效果,可改回mode="out-in"。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 04:20:18