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

