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

React模态框中Framer Motion退出动画失效问题求助

修复Framer Motion模态框关闭动画失效问题

问题背景

使用React + Framer Motion开发的模态框,打开动画正常,但关闭时直接突兀消失,完全不执行退出动画。尝试调整AnimatePresence的定位和mode参数、查阅相关问题后仍未解决,以下是组件代码、CSS样式及调用逻辑,寻求修复方案。

组件代码

import React, { ReactNode } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import styles from './StickyModal.module.scss';

interface StickyModalProps {
  isOpen: boolean;
  onClose: () => void;
  children: ReactNode;
  title: string;
}

const StickyModal: React.FC<StickyModalProps> = ({
  isOpen,
  onClose,
  title,
  children,
}) => {
  return (
    <AnimatePresence mode="wait">
      {isOpen && (
        <motion.div
          className={styles.backdrop}
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          onClick={e => {
            if (e.target === e.currentTarget) {
              onClose();
            }
          }}
        >
          <motion.div
            className={styles.modal}
            initial={{ y: '100%' }}
            animate={{ y: 0 }}
            exit={{ y: '100%' }}
            transition={{ type: 'spring', stiffness: 200, damping: 20 }}
          >
            <span>{title}</span>
            <button className={styles.closeButton} onClick={onClose}>
              Закрыть
            </button>
            {children}
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
};

export default StickyModal;

CSS样式

.backdrop {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100lvh;
  overflow-y: auto;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  border: none;
  z-index: 1000;
}

.modal {
  background-color: white;
  padding: 50px 20px;
  width: 100%;
  max-height: 70vh;
  border-radius: 30px 30px 0 0;
  position: fixed;
  bottom: -10px;
  left: 0;
  //overflow: hidden;
}

.closeButton {
  margin-top: 10px;
}

模态框调用代码

import { useState } from 'react';

export interface IResumeStatisticExtended {
  linkTitle: string | null;
  link: string | null;
}
const StatsTable = () => {
  const [selectedItem, setSelectedItem] = useState<IResumeStatisticExtended | null>(null);

  const handleAction = (index: number): void => {
    setSelectedItem(modifiedArrayStats[index]);
  };

  const closeModal = (): void => {
    setSelectedItem(null);
  };

  return (
    <div>
      <StatsTableColumn
        array={renderColumnItems('status')}
        title="Подробнее"
        hasError={modifiedArrayStats.some(hasError)}
        onAction={handleAction}
      />

      {selectedItem && (
        <StickyModal
          isOpen={true}
          onClose={closeModal}
          title={formatDateTime(selectedItem.date_time)}
        >
          <div>
            <p>{selectedItem.fact}</p>
          </div>
        </StickyModal>
      )}
    </div>
  );
};

export default StatsTable;

问题根源

  1. 模态框挂载逻辑错误:父组件通过selectedItem && <StickyModal>做条件渲染,关闭时会直接卸载整个StickyModal组件,导致内部AnimatePresence无法执行退出动画。
  2. 缺少唯一标识key:AnimatePresence包裹的动态元素没有唯一key,Framer Motion无法准确追踪元素的创建/销毁状态,无法触发退出动画。

修复步骤

1. 调整模态框调用逻辑

父组件不再直接条件渲染StickyModal,改为始终渲染组件,通过isOpen属性控制显示:

// StatsTable组件返回部分修改
return (
  <div>
    <StatsTableColumn
      array={renderColumnItems('status')}
      title="Подробнее"
      hasError={modifiedArrayStats.some(hasError)}
      onAction={handleAction}
    />

    {/* 移除条件渲染,始终渲染StickyModal,用isOpen控制显示 */}
    <StickyModal
      isOpen={!!selectedItem}
      onClose={closeModal}
      title={selectedItem ? formatDateTime(selectedItem.date_time) : ''}
    >
      {selectedItem && (
        <div>
          <p>{selectedItem.fact}</p>
        </div>
      )}
    </StickyModal>
  </div>
);

2. 给动态元素添加唯一key

在StickyModal组件中,给AnimatePresence包裹的外层motion.div添加唯一key:

<AnimatePresence mode="wait">
  {isOpen && (
    <motion.div
      key="modal-backdrop" {/* 添加唯一key */}
      className={styles.backdrop}
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      onClick={e => {
        if (e.target === e.currentTarget) {
          onClose();
        }
      }}
    >
      {/* 内部modal代码保持不变 */}
    </motion.div>
  )}
</AnimatePresence>

3. 优化CSS避免定位冲突(可选)

模态框的fixed定位与y轴动画可能存在叠加干扰,调整CSS让动画更流畅:

.modal {
  background-color: white;
  padding: 50px 20px;
  width: 100%;
  max-height: 70vh;
  border-radius: 30px 30px 0 0;
  position: fixed;
  bottom: 0; /* 改为固定在底部 */
  left: 0;
}

修复原理

  • AnimatePresence必须直接控制条件渲染的元素,而非父组件直接卸载整个组件,这样才能捕获元素的销毁状态并执行退出动画。
  • 唯一key让Framer Motion能精准区分元素的创建与销毁,确保动画触发逻辑正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:00:13