motion-framer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion & Framer Motion
Motion & Framer Motion
Overview
概述
Motion (formerly Framer Motion) is a production-ready animation library for React and JavaScript that enables declarative, performant animations with minimal code. It provides 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.
motionWhen to use this skill:
- Building interactive UI components (buttons, cards, menus)
- Creating micro-interactions and hover effects
- Implementing page transitions and route animations
- Adding scroll-based animations and parallax effects
- Animating layout changes (resizing, reordering, shared element transitions)
- Drag-and-drop interfaces
- Complex animation sequences and state-based animations
- Replacing CSS transitions with more powerful, controllable animations
Technology:
- Motion (v11+) - The modern, smaller library from Framer Motion creators
- Framer Motion - The full-featured predecessor (still widely used)
- React 18+ compatible, also supports Vue
- Supports TypeScript
- Works with Next.js, Vite, Remix, and all modern React frameworks
Motion(前身为Framer Motion)是一款适用于React和JavaScript的可用于生产环境的动画库,只需少量代码即可实现声明式、高性能的动画。它提供的组件可为HTML元素赋予动画能力,支持手势识别(悬停、点击、拖拽、聚焦),还包含布局动画、退场动画、弹簧物理效果等高级功能。
motion适用场景:
- 构建交互式UI组件(按钮、卡片、菜单)
- 创建微交互和悬停效果
- 实现页面过渡和路由动画
- 添加基于滚动的动画和视差效果
- 为布局变化(尺寸调整、重新排序、共享元素过渡)添加动画
- 拖拽交互界面
- 复杂动画序列和基于状态的动画
- 用更强大、可控的动画替代CSS过渡
相关技术:
- Motion (v11+) - 由Framer Motion开发者推出的轻量化现代库
- Framer Motion - 功能完整的前代版本(仍被广泛使用)
- 兼容React 18+,同时支持Vue
- 支持TypeScript
- 可与Next.js、Vite、Remix及所有现代React框架配合使用
Core Concepts
核心概念
1. Motion Components
1. Motion组件
Convert any HTML/SVG element into an animatable component by prefixing with :
motion.jsx
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 , , , and gesture props like , , etc.
animateinitialtransitionwhileHoverwhileTap通过在HTML/SVG元素前添加前缀,将其转换为可动画组件:
motion.jsx
import { motion } from "framer-motion"
// 普通HTML元素变为motion组件
<motion.div />
<motion.button />
<motion.svg />
<motion.path />每个motion组件都支持、、等动画属性,以及、等手势属性。
animateinitialtransitionwhileHoverwhileTap2. Animate Prop
2. Animate属性
The prop defines the target animation state. When values change, Motion automatically animates to them:
animatejsx
// 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 }} />animatejsx
// 简单动画 - 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 }} />3. Initial State
3. 初始状态
Set the initial state before animation using the prop:
initialjsx
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>Set to disable initial animations on mount.
initial={false}使用属性设置动画开始前的初始状态:
initialjsx
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>设置可禁用组件挂载时的初始动画。
initial={false}4. Transitions
4. 过渡效果
Control how animations move between states using the prop:
transitionjsx
// 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:
- (default) - Duration-based with easing
"tween" - - Physics-based spring animation
"spring" - - Decelerating animation (used in drag)
"inertia"
使用属性控制动画在不同状态间的过渡方式:
transitionjsx
// 基于时长的过渡
<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"
5. Variants
5. 变体
Organize animation states using named variants for cleaner code and propagation to children:
jsx
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:
jsx
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>使用命名变体组织动画状态,让代码更简洁,且可将状态传递给子组件:
jsx
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"
/>变体传播 - 子组件会自动继承父组件的变体状态:
jsx
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>Common Patterns
常见模式
1. Hover Animations
1. 悬停动画
Animate on hover using prop:
whileHoverjsx
// 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:
jsx
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>Title</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>使用属性实现悬停时的动画:
whileHoverjsx
// 简单悬停效果
<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>包含嵌套元素的悬停动画:
jsx
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>标题</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>2. Tap/Press Animations
2. 点击/按压动画
Animate on tap/press using prop:
whileTapjsx
// 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>使用属性实现点击/按压时的动画:
whileTapjsx
// 点击时缩小
<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>3. Drag Interactions
3. 拖拽交互
Make elements draggable with the prop:
dragjsx
// 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:
jsx
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>使用属性让元素可拖拽:
dragjsx
// 基础拖拽(支持双轴)
<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 }}
/>拖拽事件:
jsx
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>事件信息对象包含:
- - 页面坐标
point: { x, y } - - 相对于拖拽起始点的偏移量
offset: { x, y } - - 拖拽速度
velocity: { x, y }
4. Exit Animations (AnimatePresence)
4. 退场动画(AnimatePresence)
Animate components when they're removed from the DOM using :
AnimatePresencejsx
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:
- Component must be direct child of
<AnimatePresence> - Must have a unique prop
key - Use prop to define exit animation
exit
List items with exit animations:
jsx
<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:
jsx
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>使用实现组件从DOM中移除时的动画:
AnimatePresencejsx
import { AnimatePresence } from "framer-motion"
// 基础退场动画
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>关键要求:
- 组件必须是的直接子元素
<AnimatePresence> - 必须拥有唯一的属性
key - 使用属性定义退场动画
exit
列表项的退场动画:
jsx
<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>依次触发的退场动画:
jsx
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>5. Layout Animations
5. 布局动画
Automatically animate layout changes (position, size) with the prop:
layoutjsx
// 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:
jsx
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 :
layoutIdjsx
// 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>使用属性自动为布局变化(位置、尺寸)添加动画:
layoutjsx
// 为所有布局变化添加动画
<motion.div layout />
// 仅为位置变化添加动画
<motion.div layout="position" />
// 仅为尺寸变化添加动画
<motion.div layout="size" />网格布局动画:
jsx
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):
使用连接两个不同元素,实现平滑过渡:
layoutIdjsx
// 标签指示器示例
<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>6. Scroll-Based Animations
6. 基于滚动的动画
Animate elements when they enter the viewport using :
whileInViewjsx
<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:
- - Animation triggers only once
once: true - - Percentage of element visible (0-1) or "some" | "all"
amount: 0.5 - - Offset viewport boundaries
margin: "-100px"
Staggered scroll animations:
jsx
<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>使用属性在元素进入视口时触发动画:
whileInViewjsx
<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 - - 元素可见比例(0-1)或 "some" | "all"
amount: 0.5 - - 视口边界偏移量
margin: "-100px"
依次触发的滚动动画:
jsx
<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>7. Spring Animations
7. 弹簧动画
Use spring physics for natural, bouncy animations:
jsx
// 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:
- Gentle:
stiffness: 100, damping: 20 - Wobbly:
stiffness: 200, damping: 10 - Stiff:
stiffness: 400, damping: 30 - Slow:
stiffness: 50, damping: 20
使用弹簧物理效果实现自然、有弹性的动画:
jsx
// 基础弹簧动画
<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)
}}
/>弹簧预设:
- Gentle:
stiffness: 100, damping: 20 - Wobbly:
stiffness: 200, damping: 10 - Stiff:
stiffness: 400, damping: 30 - Slow:
stiffness: 50, damping: 20
Gesture Recognition
手势识别
Motion provides declarative gesture handlers:
Motion提供声明式的手势处理:
Gesture Props
手势属性
jsx
<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
/>jsx
<motion.div
whileHover={{ scale: 1.1 }} // 指针悬停在元素上
whileTap={{ scale: 0.9 }} // 主指针按压元素
whileFocus={{ outline: "2px" }} // 元素获得焦点
whileDrag={{ scale: 1.1 }} // 元素正在被拖拽
whileInView={{ opacity: 1 }} // 元素在视口内
/>Gesture Events
手势事件
jsx
<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:
- - Page coordinates
point: { x, y } - - Offset from drag start
offset: { x, y } - - Drag velocity
velocity: { x, y }
jsx
<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 }
Hooks
Hooks
useAnimate
useAnimate
Manually control animations with the hook:
useAnimatejsx
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:
jsx
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0 // Seek to start使用钩子手动控制动画:
useAnimatejsx
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>Item 1</li>
<li>Item 2</li>
</ul>
<button className="button">Click</button>
</div>
)
}动画控制:
jsx
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0 // 跳转到动画起始位置useSpring
useSpring
Create spring-animated motion values:
jsx
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>
)
}创建基于弹簧动画的motion值:
jsx
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>
)
}useInView
useInView
Detect when an element is in viewport:
jsx
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>
)
}检测元素是否在视口内:
jsx
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>
)
}Integration Patterns
集成模式
With GSAP
与GSAP结合
Combine Motion for React state-based animations and GSAP for complex timelines:
jsx
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}
/>
)
}使用Motion处理基于React状态的动画,使用GSAP处理复杂时间线:
jsx
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}
/>
)
}With React Three Fiber
与React Three Fiber结合
Animate 3D objects using Motion values:
jsx
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 }}
/>
</>
)
}使用Motion值为3D对象添加动画:
jsx
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 }}
/>
</>
)
}With Form Libraries
与表单库结合
Animate form validation states:
jsx
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>
)
}为表单验证状态添加动画:
jsx
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>
)
}Performance Optimization
性能优化
1. Use Transform Properties
1. 使用变换属性
Transform properties (x, y, scale, rotate) are hardware-accelerated:
jsx
// ✅ Good - Hardware accelerated
<motion.div animate={{ x: 100, scale: 1.2 }} />
// ❌ Avoid - Triggers layout/paint
<motion.div animate={{ left: 100, width: 200 }} />变换属性(x、y、scale、rotate)支持硬件加速:
jsx
// ✅ 推荐 - 硬件加速
<motion.div animate={{ x: 100, scale: 1.2 }} />
// ❌ 避免 - 触发布局/重绘
<motion.div animate={{ left: 100, width: 200 }} />2. Individual Transform Properties
2. 独立变换属性
Motion supports individual transform properties for cleaner code:
jsx
// 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)" }} />Motion支持独立设置变换属性,让代码更简洁:
jsx
// 独立属性(Motion特性)
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />
// 传统写法(同样支持)
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />3. Reduce Motion for Accessibility
3. 适配减少动画的用户偏好
Respect user preferences for reduced motion:
jsx
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}尊重用户对减少动画的系统偏好:
jsx
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}4. Layout Animations Performance
4. 布局动画性能优化
Layout animations can be expensive. Optimize with:
jsx
// Specify what to animate
<motion.div layout="position" /> // Only position, not size
// Optimize transition
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>布局动画可能消耗较多性能,可通过以下方式优化:
jsx
// 指定动画类型
<motion.div layout="position" /> // 仅为位置变化添加动画,不包含尺寸
// 优化过渡效果
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>5. Use layoutId Sparingly
5. 谨慎使用layoutId
layoutIdlayoutIdCommon Pitfalls
常见陷阱
1. Forgetting AnimatePresence for Exit Animations
1. 退场动画未使用AnimatePresence
Problem: Exit animations don't work
jsx
// ❌ Wrong - No AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}jsx
// ✅ Correct - Wrapped in AnimatePresence
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>问题: 退场动画不生效
jsx
// ❌ 错误 - 未使用AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}jsx
// ✅ 正确 - 包裹在AnimatePresence中
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>2. Missing key Prop in Lists
2. 列表项缺少key属性
Problem: AnimatePresence can't track elements
jsx
// ❌ Wrong - No key
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>jsx
// ✅ Correct - Unique keys
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>问题: AnimatePresence无法跟踪元素
jsx
// ❌ 错误 - 无key属性
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>jsx
// ✅ 正确 - 添加唯一key
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>3. Animating Non-Transform Properties
3. 动画非变换属性
Problem: Janky animations, poor performance
jsx
// ❌ Avoid - Not hardware accelerated
<motion.div animate={{ top: 100, left: 50, width: 200 }} />jsx
// ✅ Better - Use transforms
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />问题: 动画卡顿,性能差
jsx
// ❌ 避免 - 不支持硬件加速
<motion.div animate={{ top: 100, left: 50, width: 200 }} />jsx
// ✅ 推荐 - 使用变换属性
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />4. Overusing Layout Animations
4. 过度使用布局动画
Problem: Performance issues with many layout-animated elements
jsx
// ❌ Too many layout animations
{items.map(item => <motion.div layout>{item}</motion.div>)}jsx
// ✅ Use layout only where needed, optimize others
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // Cheaper animation
exit={{ opacity: 0 }}
/>
))}问题: 大量使用布局动画导致性能问题
jsx
// ❌ 过多布局动画
{items.map(item => <motion.div layout>{item}</motion.div>)}jsx
// ✅ 仅在必要时使用布局动画,优化其他动画
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // 性能消耗更低的动画
exit={{ opacity: 0 }}
/>
))}5. Not Using Variants for Complex Animations
5. 复杂动画未使用变体
Problem: Duplicated animation code, no child orchestration
jsx
// ❌ Repetitive
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />jsx
// ✅ 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" />问题: 动画代码重复,无法协调子组件动画
jsx
// ❌ 重复代码
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />jsx
// ✅ 使用变体
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
}
<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />6. Incorrect Transition Timing
6. 过渡时机设置错误
Problem: Transitions don't apply to specific gestures
jsx
// ❌ Wrong - General transition won't apply to whileHover
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }} // This applies to animate prop, not whileHover
/>jsx
// ✅ 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
/>问题: 过渡效果未应用到指定手势
jsx
// ❌ 错误 - 全局过渡不会应用到whileHover
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }} // 此过渡仅应用于animate属性,不包括whileHover
/>jsx
// ✅ 正确 - 在whileHover中设置过渡,或单独指定手势过渡
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 0.2 } // 应用于悬停开始时的动画
}}
transition={{ duration: 0.5 }} // 应用于悬停结束时的动画
/>Resources
资源
Official Documentation
官方文档
- Motion Docs - Official Motion documentation
- Framer Motion Docs - Framer Motion (legacy)
- Motion GitHub - Source code & examples
- Motion Docs - Motion官方文档
- Framer Motion Docs - Framer Motion(旧版)
- Motion GitHub - 源码及示例
Bundled Resources
配套资源
This skill includes:
references/
- - Complete Motion API reference
api_reference.md - - Variant patterns and orchestration
variants_patterns.md - - Comprehensive gesture handling guide
gesture_guide.md
scripts/
- - Generate Motion component boilerplate
animation_generator.py - - Interactive variant configuration tool
variant_builder.py
assets/
- - Complete Motion + Vite starter template
starter_motion/ - - Real-world Motion component patterns
examples/
本技能包含:
references/
- - 完整的Motion API参考
api_reference.md - - 变体模式及协调指南
variants_patterns.md - - 全面的手势处理指南
gesture_guide.md
scripts/
- - 生成Motion组件模板代码
animation_generator.py - - 交互式变体配置工具
variant_builder.py
assets/
- - 完整的Motion + Vite启动模板
starter_motion/ - - 真实场景下的Motion组件模式
examples/
Community Resources
社区资源
- Motion Dev Discord - Official community
- Framer Motion Examples - Interactive examples
- Motion Recipes - Common patterns
- CodeSandbox Templates - Live demos
- Motion Dev Discord - 官方社区
- Framer Motion Examples - 交互式示例
- Motion Recipes - 常见模式
- CodeSandbox Templates - 在线演示