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

Framer Motion AnimatePresence动画卡顿问题排查求助

问题描述

我用React和Framer Motion做了个人作品集,项目板块里点击项目卡片会打开详情模态框,用Framer Motion的AnimatePresence处理卡片到详情页的过渡动画。但动画超50%概率异常,比如图片在过渡到目标尺寸和位置前会跳变。核心代码如下:

const cardVariants = {
    hidden: { opacity: 0, y: 200 },
    show: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.5,
      },
    },
  };

  const [selectedId, setSelectedId] = useState(null);

  return (
    <>
      <motion.div
        className={"project-card " + tags.join(" ")}
        variants={cardVariants}
        layoutId={title}
        onClick={() => setSelectedId(title)}
      >
        <img src={thumb} />
        <div className="project-title">
          <motion.h3>{title}</motion.h3>
        </div>
      </motion.div>
      <AnimatePresence>
        {selectedId && (
          <motion.div className="modal-wrapper flex-center">
            <motion.div layoutId={selectedId} className="project-details">
              [...project detail content]
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </>
  );

想请教:

  1. AnimatePresence的实现是否有问题?
  2. 卡片和模态框用不同图片,是否超出AnimatePresence的设计范围?
问题分析与解决方案

1. AnimatePresence的实现问题

你的代码里,AnimatePresence本身用法没问题,但**layoutId绑定的是整个卡片/模态框容器**,而卡片里是缩略图、模态框里是详情图,两个容器的内部内容结构、尺寸差异极大——尤其是详情图未加载完成时,模态框容器的尺寸还没确定,过渡过程中容器尺寸会突然跳变,导致动画异常。

另外,你没给模态框设置退出动画,也没处理卡片隐藏和模态框入场的动画协同,状态切换时容易出现视觉冲突。

2. 不同图片是否超出设计范围

完全没有。Framer Motion的layoutId支持不同内容的元素做过渡,只是需要明确过渡的目标:要么让图片单独过渡,容器做辅助动画;要么统一容器的尺寸计算逻辑。

修复建议

方案一:让图片单独做layoutId过渡

把卡片和模态框里的图片用同一个layoutId绑定,容器的过渡单独处理,避免内容差异导致的容器尺寸跳变:

// 卡片部分
<motion.div className="project-card" onClick={() => setSelectedId(title)}>
  <motion.img 
    src={thumb} 
    layoutId={`project-image-${title}`}
  />
  <div className="project-title">
    <motion.h3>{title}</motion.h3>
  </div>
</motion.div>

// 模态框部分
<AnimatePresence mode="wait">
  {selectedId && (
    <motion.div 
      className="modal-wrapper flex-center"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.3 }}
    >
      <motion.div 
        className="project-details"
        initial={{ scale: 0.8, opacity: 0 }}
        animate={{ scale: 1, opacity: 1 }}
        exit={{ scale: 0.8, opacity: 0 }}
        transition={{ duration: 0.4 }}
      >
        <motion.img 
          src={detailImage} 
          layoutId={`project-image-${selectedId}`}
        />
        [...其他详情内容]
      </motion.div>
    </motion.div>
  )}
</AnimatePresence>

这样图片会单独完成尺寸/位置过渡,模态框容器做淡入淡出和缩放动画,视觉上更流畅。

方案二:统一容器过渡的尺寸计算

如果要让整个容器做过渡,需要确保模态框初始尺寸和卡片一致:

  • 给卡片添加ref,点击时记录卡片的位置和尺寸
  • 模态框初始状态设置为卡片的尺寸和位置,再动画到目标尺寸
  • 给模态框里的详情图添加initial={{ opacity: 0 }},等容器过渡完成后再显示,避免图片加载导致的尺寸突变

额外优化点

  • 给详情图添加预加载逻辑,或者设置loading="lazy"+固定宽高,避免过渡时图片未加载导致的尺寸跳变
  • 卡片点击后,临时设置visibility: hidden,避免过渡时卡片和模态框同时显示的视觉混乱
  • 给AnimatePresence加mode="wait",确保退出动画完成后再移除元素,避免状态切换冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 22:50:17