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

React组件挂载卸载动画实现及类切换失效解决方案

问题原因

你的代码存在两个核心逻辑问题,导致动画无法正常触发:

  1. 类名绑定逻辑错误:渲染逻辑中只有pokemonDetails === true时才会给容器追加动画相关类,关闭流程中等待500ms后你直接将pokemonDetails设为false,容器会立刻移除所有样式(包括fadeOut),退场动画根本没有播放的机会。
  2. 入场动画缺少触发时机:打开组件时你同时设置fadeIn类和切换pokemonDetails为true,React会合并这两次状态更新,浏览器首次渲染该DOM节点时会直接计算最终样式,不会产生从0高度到目标高度的变化过程,入场动画直接被跳过。
现有代码修复方法

你需要拆分状态,把「组件是否挂载到DOM」和「当前动画阶段」两个逻辑分开控制:

  1. 入场时先挂载DOM,等浏览器完成首次渲染后再添加入场动画类,触发样式变化产生动画
  2. 退场时先切换为退场动画类,等动画播放完成后再从DOM中移除组件

修复后的逻辑代码:

// 拆分两个独立状态,不要用一个布尔值同时控制挂载和动画
const [isMounted, setIsMounted] = useState(false);
const [animationClass, setAnimationClass] = useState("");

const handlePokemonDetails = () => {
  if (isMounted) {
    // 退场流程:先切换退场动画类
    setAnimationClass(styles.fadeOut);
    // 等待动画播放完成后再移除DOM
    setTimeout(() => {
      setIsMounted(false);
      setAnimationClass("");
    }, 500);
  } else {
    // 入场流程:先把组件挂载到DOM
    setIsMounted(true);
    // 等待浏览器完成首帧渲染后再添加入场类,触发重排产生动画
    requestAnimationFrame(() => {
      setAnimationClass(styles.fadeIn);
    });
  }
};

调整渲染逻辑,只要组件处于挂载状态就保留基础样式类:

{isMounted && (
  <div className={`${styles.pokemonDetails} ${animationClass}`}></div>
)}

最后给SASS动画补上forwards填充模式,避免动画播放结束后回到初始状态:

.fadeIn
  animation: fade 0.5s normal forwards

.fadeOut
  animation: fade 0.5s reverse forwards
更可靠的生产级方案

手动维护状态同步动画时长很容易出现逻辑错漏,推荐使用生态内成熟的动画库减少重复代码:

  • 轻量场景:react-transition-group
    这是React官方维护的过渡动画基础库,专门处理组件挂载/卸载阶段的类名切换逻辑,你不需要手动写定时器等待动画完成。基础用法如下:

    import { CSSTransition } from 'react-transition-group';
    
    // 组件渲染部分
    <CSSTransition
      in={pokemonDetails}
      timeout={500}
      classNames="fade"
      unmountOnExit
    >
      <div className={styles.pokemonDetails}></div>
    </CSSTransition>
    

    对应SASS只需要遵循库的类名约定即可,不需要写@keyframes:

    // 入场初始状态
    .fade-enter {
      height: 0vmin;
    }
    // 入场动画执行阶段
    .fade-enter-active {
      height: 39.5vmin;
      transition: height 0.5s;
    }
    // 退场初始状态
    .fade-exit {
      height: 39.5vmin;
    }
    // 退场动画执行阶段
    .fade-exit-active {
      height: 0vmin;
      transition: height 0.5s;
    }
    
  • 复杂动画场景:framer-motion
    如果需要实现更复杂的组合动画、手势动画,可以使用framer-motion,通过声明式的API直接定义动画状态,不需要手动维护类名:

    import { motion, AnimatePresence } from "framer-motion";
    
    // 组件渲染部分
    <AnimatePresence>
      {pokemonDetails && (
        <motion.div
          className={styles.pokemonDetails}
          initial={{ height: 0 }}
          animate={{ height: "39.5vmin" }}
          exit={{ height: 0 }}
          transition={{ duration: 0.5 }}
        />
      )}
    </AnimatePresence>
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 11:54:15