motion-framer by freshtechbro/claudedesignskills
npx skills add https://github.com/freshtechbro/claudedesignskills --skill motion-framerMotion(前身为 Framer Motion)是一个生产就绪的 React 和 JavaScript 动画库,能够以最少的代码实现声明式、高性能的动画。它提供了 motion 组件,这些组件为 HTML 元素赋予了动画超能力,支持手势识别(悬停、点击、拖拽、聚焦),并包含布局动画、退出动画和弹簧物理等高级功能。
何时使用此技能:
技术栈:
通过添加 motion. 前缀,将任何 HTML/SVG 元素转换为可动画化的组件:
import { motion } from "framer-motion"
// 常规 HTML 变为 motion 组件
<motion.div />
<motion.button />
<motion.svg />
<motion.path />
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
每个 motion 组件都接受动画属性,如 animate、initial、transition,以及手势属性,如 whileHover、whileTap 等。
animate 属性定义了目标动画状态。当值改变时,Motion 会自动动画过渡到这些值:
// 简单动画 - x 位置变化
<motion.div animate={{ x: 100 }} />
// 多个属性
<motion.div animate={{ x: 100, opacity: 1, scale: 1.2 }} />
// 状态改变时动画化
const [isOpen, setIsOpen] = useState(false)
<motion.div animate={{ width: isOpen ? 300 : 100 }} />
使用 initial 属性设置动画前的初始状态:
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>
设置 initial={false} 以禁用挂载时的初始动画。
使用 transition 属性控制动画在状态之间如何移动:
// 基于持续时间的过渡
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
// 弹簧物理
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>
// 为不同属性设置不同的过渡
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{
x: { type: "spring", stiffness: 300 },
opacity: { duration: 0.2 }
}}
/>
过渡类型:
"tween" (默认) - 基于持续时间,带有缓动效果"spring" - 基于物理的弹簧动画"inertia" - 减速动画(用于拖拽)使用命名的变体来组织动画状态,以获得更清晰的代码并传播到子元素:
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, scale: 0.9 }
}
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
/>
变体传播 - 子元素自动继承父元素的变体状态:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1 // 错开子元素动画
}
}
}
const itemVariants = {
hidden: { x: -20, opacity: 0 },
visible: { x: 0, opacity: 1 }
}
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
使用 whileHover 属性在悬停时进行动画:
// 简单的悬停效果
<motion.button
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.2 }}
>
悬停我
</motion.button>
// 多个属性
<motion.div
whileHover={{
scale: 1.05,
backgroundColor: "#f0f0f0",
boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)"
}}
>
悬停卡片
</motion.div>
// 使用自定义过渡
<motion.button
whileHover={{
scale: 1.2,
transition: { duration: 0.1 } // 手势开始的过渡
}}
transition={{ duration: 0.5 }} // 手势结束的过渡
>
按钮
</motion.button>
嵌套元素的悬停:
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>标题</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>
使用 whileTap 属性在点击/按压时进行动画:
// 点击时缩小
<motion.button
whileTap={{ scale: 0.9 }}
>
点击我
</motion.button>
// 组合悬停 + 点击
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95, rotate: 3 }}
>
交互式按钮
</motion.button>
// 使用变体
const buttonVariants = {
rest: { scale: 1 },
hover: { scale: 1.1 },
pressed: { scale: 0.95 }
}
<motion.button
variants={buttonVariants}
initial="rest"
whileHover="hover"
whileTap="pressed"
>
按钮
</motion.button>
使用 drag 属性使元素可拖拽:
// 基本拖拽(两个轴)
<motion.div drag />
// 限制到特定轴
<motion.div drag="x" /> // 仅水平
<motion.div drag="y" /> // 仅垂直
// 拖拽约束
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
/>
// 使用父元素约束进行拖拽
<motion.div ref={constraintsRef}>
<motion.div drag dragConstraints={constraintsRef} />
</motion.div>
// 拖拽时的视觉反馈
<motion.div
drag
whileDrag={{
scale: 1.1,
boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
cursor: "grabbing"
}}
dragElastic={0.1} // 拖拽超出约束时的弹性
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
/>
拖拽事件:
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>
使用 AnimatePresence 在组件从 DOM 中移除时进行动画:
import { AnimatePresence } from "framer-motion"
// 基本退出动画
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
关键要求:
<AnimatePresence> 的直接子元素key 属性exit 属性定义退出动画带有退出动画的列表项:
<AnimatePresence>
{items.map(item => (
<motion.li
key={item.id}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
layout // 平滑的布局偏移
>
{item.name}
</motion.li>
))}
</AnimatePresence>
错开的退出动画:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.1
}
},
exit: {
opacity: 0,
transition: {
when: "afterChildren",
staggerChildren: 0.05,
staggerDirection: -1 // 反向顺序
}
}
}
<AnimatePresence>
{show && (
<motion.div variants={containerVariants} initial="hidden" animate="visible" exit="exit">
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
</motion.div>
)}
</AnimatePresence>
使用 layout 属性自动动画化布局变化(位置、大小):
// 动画化所有布局变化
<motion.div layout />
// 仅动画化位置变化
<motion.div layout="position" />
// 仅动画化大小变化
<motion.div layout="size" />
网格布局动画:
const [columns, setColumns] = useState(3)
<motion.div className="grid">
{items.map(item => (
<motion.div
key={item.id}
layout
transition={{ layout: { duration: 0.3, ease: "easeInOut" } }}
/>
))}
</motion.div>
共享布局动画 (layoutId):
使用 layoutId 连接两个不同的元素以实现平滑过渡:
// 选项卡指示器示例
<nav>
{tabs.map(tab => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="underline"
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}
/>
)}
</button>
))}
</nav>
// 从缩略图打开的模态框
<motion.img
src={thumbnail}
layoutId="product-image"
onClick={() => setExpanded(true)}
/>
<AnimatePresence>
{expanded && (
<motion.div layoutId="product-image">
<img src={fullsize} />
</motion.div>
)}
</AnimatePresence>
使用 whileInView 在元素进入视口时进行动画:
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.8 }} // once: 仅触发一次, amount: 80% 可见
transition={{ duration: 0.5 }}
>
滚动进入视图时动画化
</motion.div>
视口选项:
once: true - 动画仅触发一次amount: 0.5 - 元素可见的百分比 (0-1) 或 "some" | "all"margin: "-100px" - 视口边界的偏移量错开的滚动动画:
<motion.ul
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={{
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
},
hidden: { opacity: 0 }
}}
>
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
使用弹簧物理实现自然、有弹性的动画:
// 基本弹簧
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring" }}
/>
// 自定义弹簧物理
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 300, // 值越高 = 越快、更干脆 (默认: 100)
damping: 20, // 值越高 = 弹性越小 (默认: 10)
mass: 1, // 值越高 = 惯性越大 (默认: 1)
}}
/>
// 视觉持续时间(更易控制的弹簧)
<motion.div
animate={{ rotate: 90 }}
transition={{
type: "spring",
visualDuration: 0.5, // 感知持续时间
bounce: 0.25 // 弹性 (0-1, 默认: 0.25)
}}
/>
弹簧预设:
stiffness: 100, damping: 20stiffness: 200, damping: 10stiffness: 400, damping: 30stiffness: 50, damping: 20Motion 提供了声明式的手势处理器:
<motion.div
whileHover={{ scale: 1.1 }} // 指针悬停在元素上
whileTap={{ scale: 0.9 }} // 主指针按压元素
whileFocus={{ outline: "2px" }} // 元素获得焦点
whileDrag={{ scale: 1.1 }} // 元素正在被拖拽
whileInView={{ opacity: 1 }} // 元素在视口中
/>
<motion.div
onHoverStart={(event, info) => {}}
onHoverEnd={(event, info) => {}}
onTap={(event, info) => {}}
onTapStart={(event, info) => {}}
onTapCancel={(event, info) => {}}
onDragStart={(event, info) => {}}
onDrag={(event, info) => {}}
onDragEnd={(event, info) => {}}
onViewportEnter={(entry) => {}}
onViewportLeave={(entry) => {}}
/>
事件信息对象包含:
point: { x, y } - 页面坐标offset: { x, y } - 相对于拖拽起点的偏移量velocity: { x, y } - 拖拽速度使用 useAnimate hook 手动控制动画:
import { useAnimate } from "framer-motion"
function Component() {
const [scope, animate] = useAnimate()
useEffect(() => {
// 动画化多个元素
animate([
[scope.current, { opacity: 1 }],
["li", { x: 0, opacity: 1 }, { delay: stagger(0.1) }],
[".button", { scale: 1.2 }]
])
}, [])
return (
<div ref={scope}>
<ul>
<li>项目 1</li>
<li>项目 2</li>
</ul>
<button className="button">点击</button>
</div>
)
}
动画控制:
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0 // 跳转到开始
创建弹簧动画的 motion 值:
import { useSpring } from "framer-motion"
function Component() {
const x = useSpring(0, { stiffness: 300, damping: 20 })
return (
<motion.div style={{ x }}>
<button onClick={() => x.set(100)}>移动</button>
</motion.div>
)
}
检测元素何时进入视口:
import { useInView } from "framer-motion"
function Component() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.5 })
return (
<div ref={ref}>
{isInView ? "在视口中!" : "不在视口中"}
</div>
)
}
结合 Motion 处理基于 React 状态的动画和 GSAP 处理复杂的时间线:
import { motion } from "framer-motion"
import gsap from "gsap"
function Component() {
const boxRef = useRef()
const handleClick = () => {
// 使用 GSAP 处理复杂时间线
const tl = gsap.timeline()
tl.to(boxRef.current, { rotation: 360, duration: 1 })
.to(boxRef.current, { scale: 1.5, duration: 0.5 })
}
return (
// 使用 Motion 处理悬停/点击/布局动画
<motion.div
ref={boxRef}
whileHover={{ scale: 1.1 }}
onClick={handleClick}
/>
)
}
使用 Motion 值动画化 3D 对象:
import { motion } from "framer-motion"
import { useFrame } from "@react-three/fiber"
function Box() {
const x = useMotionValue(0)
useFrame(() => {
// 将 Motion 值与 Three.js 位置同步
meshRef.current.position.x = x.get()
})
return (
<>
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<motion.div
style={{ x }}
drag="x"
dragConstraints={{ left: -5, right: 5 }}
/>
</>
)
}
动画化表单验证状态:
import { motion, AnimatePresence } from "framer-motion"
function FormField({ error }) {
return (
<div>
<motion.input
animate={{
borderColor: error ? "#ff0000" : "#cccccc",
x: error ? [0, -10, 10, -10, 10, 0] : 0 // 抖动动画
}}
transition={{ duration: 0.4 }}
/>
<AnimatePresence>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
style={{ color: "#ff0000" }}
>
{error}
</motion.p>
)}
</AnimatePresence>
</div>
)
}
变换属性 (x, y, scale, rotate) 是硬件加速的:
// ✅ 良好 - 硬件加速
<motion.div animate={{ x: 100, scale: 1.2 }} />
// ❌ 避免 - 触发布局/绘制
<motion.div animate={{ left: 100, width: 200 }} />
Motion 支持独立的变换属性,以获得更清晰的代码:
// 独立属性 (Motion 特性)
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />
// 传统方式 (也支持)
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />
尊重用户减少动画的偏好:
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}
布局动画可能开销较大。通过以下方式优化:
// 指定要动画化的内容
<motion.div layout="position" /> // 仅位置,不包括大小
// 优化过渡
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>
layoutId 创建共享布局动画,但会全局跟踪元素。仅在需要时使用。
问题: 退出动画不起作用
// ❌ 错误 - 没有 AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}
// ✅ 正确 - 包裹在 AnimatePresence 中
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
问题: AnimatePresence 无法跟踪元素
// ❌ 错误 - 没有 key
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>
// ✅ 正确 - 唯一的 key
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>
问题: 动画卡顿,性能差
// ❌ 避免 - 非硬件加速
<motion.div animate={{ top: 100, left: 50, width: 200 }} />
// ✅ 更好 - 使用变换
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />
问题: 大量布局动画元素导致性能问题
// ❌ 太多布局动画
{items.map(item => <motion.div layout>{item}</motion.div>)}
// ✅ 仅在需要的地方使用布局动画,优化其他部分
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // 开销更小的动画
exit={{ opacity: 0 }}
/>
))}
问题: 重复的动画代码,没有子元素编排
// ❌ 重复
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
// ✅ 使用变体
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
}
<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />
问题: 过渡不应用于特定手势
// ❌ 错误 - 通用过渡不会应用于 whileHover
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }} // 这应用于 animate 属性,而非 whileHover
/>
// ✅ 正确 - 在 whileHover 中设置过渡或单独的手势过渡
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 0.2 } // 应用于悬停开始
}}
transition={{ duration: 0.5 }} // 应用于悬停结束
/>
此技能包含:
references/
api_reference.md - 完整的 Motion API 参考variants_patterns.md - 变体模式和编排gesture_guide.md - 全面的手势处理指南scripts/
animation_generator.py - 生成 Motion 组件样板代码variant_builder.py - 交互式变体配置工具assets/
starter_motion/ - 完整的 Motion + Vite 启动模板examples/ - 真实世界的 Motion 组件模式每周安装量
88
仓库
GitHub 星标数
11
首次出现
2026年2月27日
安全审计
安装于
opencode87
cursor85
claude-code85
github-copilot85
amp85
cline85
Motion (formerly Framer Motion) is a production-ready animation library for React and JavaScript that enables declarative, performant animations with minimal code. It provides motion components that wrap HTML elements with animation superpowers, supports gesture recognition (hover, tap, drag, focus), and includes advanced features like layout animations, exit animations, and spring physics.
When to use this skill:
Technology:
Convert any HTML/SVG element into an animatable component by prefixing with motion.:
import { motion } from "framer-motion"
// Regular HTML becomes motion component
<motion.div />
<motion.button />
<motion.svg />
<motion.path />
Every motion component accepts animation props like animate, initial, transition, and gesture props like whileHover, whileTap, etc.
The animate prop defines the target animation state. When values change, Motion automatically animates to them:
// Simple animation - x position changes
<motion.div animate={{ x: 100 }} />
// Multiple properties
<motion.div animate={{ x: 100, opacity: 1, scale: 1.2 }} />
// Animates when state changes
const [isOpen, setIsOpen] = useState(false)
<motion.div animate={{ width: isOpen ? 300 : 100 }} />
Set the initial state before animation using the initial prop:
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>
Set initial={false} to disable initial animations on mount.
Control how animations move between states using the transition prop:
// Duration-based
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
// Spring physics
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>
// Different transitions for different properties
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{
x: { type: "spring", stiffness: 300 },
opacity: { duration: 0.2 }
}}
/>
Transition types:
"tween" (default) - Duration-based with easing"spring" - Physics-based spring animation"inertia" - Decelerating animation (used in drag)Organize animation states using named variants for cleaner code and propagation to children:
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, scale: 0.9 }
}
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
/>
Variant propagation - Children automatically inherit parent variant states:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1 // Stagger child animations
}
}
}
const itemVariants = {
hidden: { x: -20, opacity: 0 },
visible: { x: 0, opacity: 1 }
}
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
Animate on hover using whileHover prop:
// Simple hover effect
<motion.button
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.2 }}
>
Hover me
</motion.button>
// Multiple properties
<motion.div
whileHover={{
scale: 1.05,
backgroundColor: "#f0f0f0",
boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)"
}}
>
Hover card
</motion.div>
// With custom transition
<motion.button
whileHover={{
scale: 1.2,
transition: { duration: 0.1 } // Transition for gesture start
}}
transition={{ duration: 0.5 }} // Transition for gesture end
>
Button
</motion.button>
Hover with nested elements:
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>Title</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>
Animate on tap/press using whileTap prop:
// Scale down on tap
<motion.button
whileTap={{ scale: 0.9 }}
>
Click me
</motion.button>
// Combined hover + tap
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95, rotate: 3 }}
>
Interactive button
</motion.button>
// With variants
const buttonVariants = {
rest: { scale: 1 },
hover: { scale: 1.1 },
pressed: { scale: 0.95 }
}
<motion.button
variants={buttonVariants}
initial="rest"
whileHover="hover"
whileTap="pressed"
>
Button
</motion.button>
Make elements draggable with the drag prop:
// Basic dragging (both axes)
<motion.div drag />
// Constrain to axis
<motion.div drag="x" /> // Only horizontal
<motion.div drag="y" /> // Only vertical
// Drag constraints
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
/>
// Drag with parent constraints
<motion.div ref={constraintsRef}>
<motion.div drag dragConstraints={constraintsRef} />
</motion.div>
// Visual feedback while dragging
<motion.div
drag
whileDrag={{
scale: 1.1,
boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
cursor: "grabbing"
}}
dragElastic={0.1} // Elasticity when dragging outside constraints
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
/>
Drag events:
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>
Animate components when they're removed from the DOM using AnimatePresence:
import { AnimatePresence } from "framer-motion"
// Basic exit animation
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
Key requirements:
<AnimatePresence>key propexit prop to define exit animationList items with exit animations:
<AnimatePresence>
{items.map(item => (
<motion.li
key={item.id}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
layout // Smooth layout shifts
>
{item.name}
</motion.li>
))}
</AnimatePresence>
Staggered exit animations:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.1
}
},
exit: {
opacity: 0,
transition: {
when: "afterChildren",
staggerChildren: 0.05,
staggerDirection: -1 // Reverse order
}
}
}
<AnimatePresence>
{show && (
<motion.div variants={containerVariants} initial="hidden" animate="visible" exit="exit">
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
</motion.div>
)}
</AnimatePresence>
Automatically animate layout changes (position, size) with the layout prop:
// Animate all layout changes
<motion.div layout />
// Animate only position changes
<motion.div layout="position" />
// Animate only size changes
<motion.div layout="size" />
Grid layout animation:
const [columns, setColumns] = useState(3)
<motion.div className="grid">
{items.map(item => (
<motion.div
key={item.id}
layout
transition={{ layout: { duration: 0.3, ease: "easeInOut" } }}
/>
))}
</motion.div>
Shared layout animations (layoutId):
Connect two different elements for smooth transitions using layoutId:
// Tab indicator example
<nav>
{tabs.map(tab => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="underline"
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}
/>
)}
</button>
))}
</nav>
// Modal opening from thumbnail
<motion.img
src={thumbnail}
layoutId="product-image"
onClick={() => setExpanded(true)}
/>
<AnimatePresence>
{expanded && (
<motion.div layoutId="product-image">
<img src={fullsize} />
</motion.div>
)}
</AnimatePresence>
Animate elements when they enter the viewport using whileInView:
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.8 }} // once: trigger once, amount: 80% visible
transition={{ duration: 0.5 }}
>
Animates when scrolled into view
</motion.div>
Viewport options:
once: true - Animation triggers only onceamount: 0.5 - Percentage of element visible (0-1) or "some" | "all"margin: "-100px" - Offset viewport boundariesStaggered scroll animations:
<motion.ul
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={{
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
},
hidden: { opacity: 0 }
}}
>
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
Use spring physics for natural, bouncy animations:
// Basic spring
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring" }}
/>
// Customize spring physics
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 300, // Higher = faster, snappier (default: 100)
damping: 20, // Higher = less bouncy (default: 10)
mass: 1, // Higher = more inertia (default: 1)
}}
/>
// Visual duration (easier spring control)
<motion.div
animate={{ rotate: 90 }}
transition={{
type: "spring",
visualDuration: 0.5, // Perceived duration
bounce: 0.25 // Bounciness (0-1, default: 0.25)
}}
/>
Spring presets:
stiffness: 100, damping: 20stiffness: 200, damping: 10stiffness: 400, damping: 30stiffness: 50, damping: 20Motion provides declarative gesture handlers:
<motion.div
whileHover={{ scale: 1.1 }} // Pointer hovers over element
whileTap={{ scale: 0.9 }} // Primary pointer presses element
whileFocus={{ outline: "2px" }} // Element gains focus
whileDrag={{ scale: 1.1 }} // Element is being dragged
whileInView={{ opacity: 1 }} // Element is in viewport
/>
<motion.div
onHoverStart={(event, info) => {}}
onHoverEnd={(event, info) => {}}
onTap={(event, info) => {}}
onTapStart={(event, info) => {}}
onTapCancel={(event, info) => {}}
onDragStart={(event, info) => {}}
onDrag={(event, info) => {}}
onDragEnd={(event, info) => {}}
onViewportEnter={(entry) => {}}
onViewportLeave={(entry) => {}}
/>
Event info objects contain:
point: { x, y } - Page coordinatesoffset: { x, y } - Offset from drag startvelocity: { x, y } - Drag velocityManually control animations with the useAnimate hook:
import { useAnimate } from "framer-motion"
function Component() {
const [scope, animate] = useAnimate()
useEffect(() => {
// Animate multiple elements
animate([
[scope.current, { opacity: 1 }],
["li", { x: 0, opacity: 1 }, { delay: stagger(0.1) }],
[".button", { scale: 1.2 }]
])
}, [])
return (
<div ref={scope}>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button className="button">Click</button>
</div>
)
}
Animation controls:
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0 // Seek to start
Create spring-animated motion values:
import { useSpring } from "framer-motion"
function Component() {
const x = useSpring(0, { stiffness: 300, damping: 20 })
return (
<motion.div style={{ x }}>
<button onClick={() => x.set(100)}>Move</button>
</motion.div>
)
}
Detect when an element is in viewport:
import { useInView } from "framer-motion"
function Component() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.5 })
return (
<div ref={ref}>
{isInView ? "In view!" : "Not in view"}
</div>
)
}
Combine Motion for React state-based animations and GSAP for complex timelines:
import { motion } from "framer-motion"
import gsap from "gsap"
function Component() {
const boxRef = useRef()
const handleClick = () => {
// Use GSAP for complex timeline
const tl = gsap.timeline()
tl.to(boxRef.current, { rotation: 360, duration: 1 })
.to(boxRef.current, { scale: 1.5, duration: 0.5 })
}
return (
// Use Motion for hover/tap/layout animations
<motion.div
ref={boxRef}
whileHover={{ scale: 1.1 }}
onClick={handleClick}
/>
)
}
Animate 3D objects using Motion values:
import { motion } from "framer-motion"
import { useFrame } from "@react-three/fiber"
function Box() {
const x = useMotionValue(0)
useFrame(() => {
// Sync Motion value with Three.js position
meshRef.current.position.x = x.get()
})
return (
<>
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<motion.div
style={{ x }}
drag="x"
dragConstraints={{ left: -5, right: 5 }}
/>
</>
)
}
Animate form validation states:
import { motion, AnimatePresence } from "framer-motion"
function FormField({ error }) {
return (
<div>
<motion.input
animate={{
borderColor: error ? "#ff0000" : "#cccccc",
x: error ? [0, -10, 10, -10, 10, 0] : 0 // Shake animation
}}
transition={{ duration: 0.4 }}
/>
<AnimatePresence>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
style={{ color: "#ff0000" }}
>
{error}
</motion.p>
)}
</AnimatePresence>
</div>
)
}
Transform properties (x, y, scale, rotate) are hardware-accelerated:
// ✅ Good - Hardware accelerated
<motion.div animate={{ x: 100, scale: 1.2 }} />
// ❌ Avoid - Triggers layout/paint
<motion.div animate={{ left: 100, width: 200 }} />
Motion supports individual transform properties for cleaner code:
// Individual properties (Motion feature)
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />
// Traditional (also supported)
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />
Respect user preferences for reduced motion:
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}
Layout animations can be expensive. Optimize with:
// Specify what to animate
<motion.div layout="position" /> // Only position, not size
// Optimize transition
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>
layoutId creates shared layout animations but tracks elements globally. Use only when needed.
Problem: Exit animations don't work
// ❌ Wrong - No AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}
// ✅ Correct - Wrapped in AnimatePresence
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
Problem: AnimatePresence can't track elements
// ❌ Wrong - No key
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>
// ✅ Correct - Unique keys
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>
Problem: Janky animations, poor performance
// ❌ Avoid - Not hardware accelerated
<motion.div animate={{ top: 100, left: 50, width: 200 }} />
// ✅ Better - Use transforms
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />
Problem: Performance issues with many layout-animated elements
// ❌ Too many layout animations
{items.map(item => <motion.div layout>{item}</motion.div>)}
// ✅ Use layout only where needed, optimize others
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // Cheaper animation
exit={{ opacity: 0 }}
/>
))}
Problem: Duplicated animation code, no child orchestration
// ❌ Repetitive
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
// ✅ Use variants
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
}
<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />
Problem: Transitions don't apply to specific gestures
// ❌ Wrong - General transition won't apply to whileHover
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }} // This applies to animate prop, not whileHover
/>
// ✅ Correct - Transition in whileHover or separate gesture transition
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 0.2 } // Applies to hover start
}}
transition={{ duration: 0.5 }} // Applies to hover end
/>
This skill includes:
references/
api_reference.md - Complete Motion API referencevariants_patterns.md - Variant patterns and orchestrationgesture_guide.md - Comprehensive gesture handling guidescripts/
animation_generator.py - Generate Motion component boilerplatevariant_builder.py - Interactive variant configuration toolassets/
starter_motion/ - Complete Motion + Vite starter templateexamples/ - Real-world Motion component patternsWeekly Installs
88
Repository
GitHub Stars
11
First Seen
Feb 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode87
cursor85
claude-code85
github-copilot85
amp85
cline85
TanStack Table 中文指南:React 无头表格库,实现排序过滤分页
577 周安装
Intercom自动化指南:通过Rube MCP与Composio实现客户支持对话管理
69 周安装
二进制初步分析指南:使用ReVa工具快速识别恶意软件与逆向工程
69 周安装
PrivateInvestigator 道德人员查找工具 | 公开数据调查、反向搜索与背景研究
69 周安装
TorchTitan:PyTorch原生分布式大语言模型预训练平台,支持4D并行与H100 GPU加速
69 周安装
screenshot 截图技能:跨平台桌面截图工具,支持macOS/Linux权限管理与多模式捕获
69 周安装
tmux进程管理最佳实践:交互式Shell初始化、会话命名与生命周期管理
69 周安装