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

Framer Motion退出动画未触发问题排查求助

为何Framer Motion的退出动画无法触发?

问题场景

需求是实现5个容器从顶部向下、5个容器从底部向上移动至中间后消失的动画效果,但当前代码的退出动画无法正常触发。

现有组件代码

'use client';

import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';


export default function TransLayout({ children, backgroundColor }) {
  const [isVisible, setIsVisible] = React.useState(false);

  React.useEffect(() => {
    setIsVisible(true);
    return () => setIsVisible(false);
  }, [backgroundColor]);

  const anim = (variants, custom = null) => {
    return {
      initial: 'initial',
      animate: 'enter',
      exit: 'exit',  // Ensuring exit animations are added
      custom,
      variants,
    };
  };

  const columnsPiece = 5;

  return (
    <div className='page stairs' style={{ backgroundColor }}>
      <motion.div {...anim(opacity)} className='transition-background' />
      <AnimatePresence mode="wait">
        {isVisible && (
          <div key={backgroundColor} className="content-wrapper">
            <div className='transition-container'>
              {/* Top Containers */}
              {[...Array(columnsPiece)].map((_, i) => (
                <motion.div
                  key={`top-${i}`}
                  {...anim(expandTop, columnsPiece - i)}
                  className='topContainer'
                  exit={{
                    display: 'none',  // Prevents visible elements on exit
                    ...expandTop.exit(columnsPiece - i),
                  }}
                />
              ))}

              {/* Bottom Containers */}
              {[...Array(columnsPiece)].map((_, i) => (
                <motion.div
                  key={`bottom-${i}`}
                  {...anim(expandBottom, columnsPiece - i)}
                  className='bottomContainer'
                  exit={{
                    display: 'none',  // Prevents visible elements on exit
                    ...expandBottom.exit(columnsPiece - i),
                  }}
                />
              ))}
            </div>
            <motion.div
              key="content"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}  // Fade out content when exiting
              transition={{ delay: 0.5 }}
            >
              {children}
            </motion.div>
          </div>
        )}
      </AnimatePresence>
    </div>
  );
}

动画配置代码

export const expandTop = {
    initial: {
        top: '-100vh',  // Top starts off-screen
    },
    enter: (i) => ({
        top: 0,
        transition: {
            duration: 0.4,
            delay: 0.05 * i,
            ease: [0.215, 0.61, 0.355, 1],
        },
    }),
    exit: (i) => ({
        top: '-100vh',  // Moves top back off-screen when exiting
        transition: {
            duration: 0.4,
            ease: [0.215, 0.61, 0.355, 1],
        },
    }),
};

export const expandBottom = {
    initial: {
        top: '100vh',  // Bottom starts off-screen
    },
    enter: (i) => ({
        top: 0,
        transition: {
            duration: 0.4,
            delay: 0.05 * i,
            ease: [0.215, 0.61, 0.355, 1],
        },
    }),
    exit: (i) => ({
        top: '100vh',  // Moves bottom back off-screen when exiting
        transition: {
            duration: 0.4,
            ease: [0.215, 0.61, 0.355, 1],
        },
    }),
};

export const opacity = {
    initial: {
        opacity: 0.5,
    },
    enter: {
        opacity: 0,
    },
    exit: {
        opacity: 0.5,
    },
};

问题原因分析

  1. transition-background 元素不在 AnimatePresence 范围内:Framer Motion的AnimatePresence只能监控其内部包裹的元素的挂载/卸载状态,该元素在外部,所以退出动画永远不会触发。
  2. exit属性中添加了display: none:这个属性会让元素立即从视觉上消失,跳过动画执行过程——Framer Motion需要元素保持在DOM中直到动画完成,再自动移除它。
  3. content元素的key固定不变:当backgroundColor变化时,content元素的key始终是"content",AnimatePresence不会将其识别为需要替换的元素,因此不会触发exit动画。

修复方案

针对上述问题,修改代码如下:

'use client';

import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';


export default function TransLayout({ children, backgroundColor }) {
  const [isVisible, setIsVisible] = React.useState(false);

  React.useEffect(() => {
    setIsVisible(true);
    return () => setIsVisible(false);
  }, [backgroundColor]);

  const anim = (variants, custom = null) => {
    return {
      initial: 'initial',
      animate: 'enter',
      exit: 'exit',
      custom,
      variants,
    };
  };

  const columnsPiece = 5;

  return (
    <div className='page stairs' style={{ backgroundColor }}>
      <AnimatePresence mode="wait">
        {isVisible && (
          <div key={backgroundColor} className="content-wrapper">
            {/* 将transition-background移入AnimatePresence内部 */}
            <motion.div {...anim(opacity)} className='transition-background' />
            <div className='transition-container'>
              {/* Top Containers - 移除exit中的display: none */}
              {[...Array(columnsPiece)].map((_, i) => (
                <motion.div
                  key={`top-${i}`}
                  {...anim(expandTop, columnsPiece - i)}
                  className='topContainer'
                />
              ))}

              {/* Bottom Containers - 移除exit中的display: none */}
              {[...Array(columnsPiece)].map((_, i) => (
                <motion.div
                  key={`bottom-${i}`}
                  {...anim(expandBottom, columnsPiece - i)}
                  className='bottomContainer'
                />
              ))}
            </div>
            {/* 让content的key随backgroundColor变化,触发新旧元素替换 */}
            <motion.div
              key={`content-${backgroundColor}`}
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              transition={{ delay: 0.5 }}
            >
              {children}
            </motion.div>
          </div>
        )}
      </AnimatePresence>
    </div>
  );
}

额外说明

  • AnimatePresence 的 mode="wait" 会确保旧元素的退出动画完全完成后,再渲染新元素,符合你的过渡需求。
  • 动画配置无需修改,只要保证元素在AnimatePresence内部,且没有强制提前隐藏元素的属性,退出动画就能正常触发。

内容的提问来源于stack exchange,提问作者Emre Memiş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:23:11