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

如何用React与Framer-Motion实现多张卡片动画效果

解决React + Framer-Motion多张卡片展开聚焦动画问题

核心问题分析

你当前代码的主要问题在于:

  • 布局结构错误:展开的卡片直接放在grid-cols-4容器中,导致布局混乱,无法触发正确的过渡动画
  • 未正确获取选中卡片的数据:直接访问cards.subtitle无法拿到对应卡片的内容
  • 缺少必要的动画配置:未设置initial/animate/exit及transition属性,无法激活Framer-Motion的动画效果
  • layoutId未配合AnimatePresence完成状态切换:同一个layoutId的元素需要在AnimatePresence中完成显隐切换才能触发布局动画

修正后的完整代码

import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";

const JobsPage = () => {
  const [selectedId, setSelectedId] = useState(null);
  
  const cards = [
    {
      id: 1,
      subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. A, minima?',
      title: 'Card 1'
    }, 
    {
      id: 2,
      subtitle: 'Lorem ipsum dolor sit amet.',
      title: 'Card 2'
    }, 
    {
      id: 3,
      subtitle: 'Lorem ipsum dolor sit amet.',
      title: 'Card 3'
    }, 
    {
      id: 4,
      subtitle: 'Lorem ipsum dolor sit amet.',
      title: 'Card 4'
    }
  ];

  // 获取选中的卡片数据
  const selectedCard = cards.find(card => card.id === selectedId);

  return (
    <div className="">
      <h2 className="text-2xl font-bold flex justify-center mb-4">
        Jobs
      </h2>
      {/* 动态切换网格布局:选中时单列,未选中时四列 */}
      <div className={`bg-green-300 p-4 grid gap-2 ${selectedId ? 'grid-cols-1' : 'grid-cols-4'}`}>
        <AnimatePresence mode="wait">
          {!selectedId ? (
            // 未选中状态:渲染所有卡片
            cards.map(card => (
              <motion.div
                key={card.id}
                layoutId={card.id}
                className="bg-white p-3 cursor-pointer shadow-sm"
                onClick={() => setSelectedId(card.id)}
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -20 }}
                transition={{ duration: 0.3 }}
              >
                <h2 className="font-semibold">{card.title}</h2>
                <h5 className="text-sm text-gray-600 mt-1">{card.subtitle}</h5>
              </motion.div>
            ))
          ) : (
            // 选中状态:渲染展开的卡片
            <motion.div
              layoutId={selectedId}
              className="bg-white p-6 shadow-md"
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              transition={{ duration: 0.3 }}
            >
              <h2 className="text-2xl font-bold">{selectedCard.title}</h2>
              <h5 className="text-gray-600 mt-3">{selectedCard.subtitle}</h5>
              <motion.button
                className="mt-5 px-4 py-2 bg-gray-200 rounded-md"
                onClick={() => setSelectedId(null)}
                whileHover={{ scale: 1.05, backgroundColor: '#e5e7eb' }}
                whileTap={{ scale: 0.95 }}
              >
                关闭
              </motion.button>
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </div>
  );
};

export default JobsPage;

关键修改说明

  1. 动态布局切换:根据selectedId是否存在,将网格从grid-cols-4切换为grid-cols-1,让展开的卡片占据全屏宽度
  2. 动画状态管理:用AnimatePresence包裹两种状态的内容,mode="wait"确保前一组动画完成后再触发下一组,避免动画冲突
  3. 布局过渡动画:通过layoutId绑定卡片和展开后的容器,实现从卡片尺寸到全屏尺寸的平滑过渡
  4. 数据获取优化:提前用find获取选中的卡片数据,避免重复遍历数组
  5. 细节动画增强:给卡片添加进入/退出的淡入淡出+位移动画,给关闭按钮添加hover/tap的微交互动画
  6. React语法修正:将class改为React要求的className

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 09:07:55