motion-framer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motion & 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
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:
  • 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的可用于生产环境的动画库,只需少量代码即可实现声明式、高性能的动画。它提供的
motion
组件可为HTML元素赋予动画能力,支持手势识别(悬停、点击、拖拽、聚焦),还包含布局动画、退场动画、弹簧物理效果等高级功能。
适用场景:
  • 构建交互式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
animate
,
initial
,
transition
, and gesture props like
whileHover
,
whileTap
, etc.
通过在HTML/SVG元素前添加
motion.
前缀,将其转换为可动画组件:
jsx
import { motion } from "framer-motion"

// 普通HTML元素变为motion组件
<motion.div />
<motion.button />
<motion.svg />
<motion.path />
每个motion组件都支持
animate
initial
transition
等动画属性,以及
whileHover
whileTap
等手势属性。

2. Animate Prop

2. Animate属性

The
animate
prop defines the target animation state. When values change, Motion automatically animates to them:
jsx
// 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 }} />
animate
属性定义动画的目标状态。当值发生变化时,Motion会自动将元素动画过渡到新状态:
jsx
// 简单动画 - 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
initial
prop:
jsx
<motion.div
  initial={{ opacity: 0, y: 50 }}
  animate={{ opacity: 1, y: 0 }}
/>
Set
initial={false}
to disable initial animations on mount.
使用
initial
属性设置动画开始前的初始状态:
jsx
<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
transition
prop:
jsx
// 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)
使用
transition
属性控制动画在不同状态间的过渡方式:
jsx
// 基于时长的过渡
<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
whileHover
prop:
jsx
// 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>
使用
whileHover
属性实现悬停时的动画:
jsx
// 简单悬停效果
<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
whileTap
prop:
jsx
// 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>
使用
whileTap
属性实现点击/按压时的动画:
jsx
// 点击时缩小
<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
drag
prop:
jsx
// 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)}
/>
使用
drag
属性让元素可拖拽:
jsx
// 基础拖拽(支持双轴)
<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
AnimatePresence
:
jsx
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
    key
    prop
  • Use
    exit
    prop to define exit animation
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>
使用
AnimatePresence
实现组件从DOM中移除时的动画:
jsx
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
layout
prop:
jsx
// 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
layoutId
:
jsx
// 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>
使用
layout
属性自动为布局变化(位置、尺寸)添加动画:
jsx
// 为所有布局变化添加动画
<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):
使用
layoutId
连接两个不同元素,实现平滑过渡:
jsx
// 标签指示器示例
<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
whileInView
:
jsx
<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 once
  • amount: 0.5
    - Percentage of element visible (0-1) or "some" | "all"
  • margin: "-100px"
    - Offset viewport boundaries
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>
使用
whileInView
属性在元素进入视口时触发动画:
jsx
<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"
    - 视口边界偏移量
依次触发的滚动动画:
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:
  • point: { x, y }
    - Page coordinates
  • offset: { x, y }
    - Offset from drag start
  • velocity: { x, y }
    - Drag velocity
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
useAnimate
hook:
jsx
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
使用
useAnimate
钩子手动控制动画:
jsx
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

layoutId
creates shared layout animations but tracks elements globally. Use only when needed.
layoutId
用于创建共享布局动画,但会全局跟踪元素。仅在必要时使用。

Common 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

官方文档

Bundled Resources

配套资源

This skill includes:
references/
  • api_reference.md
    - Complete Motion API reference
  • variants_patterns.md
    - Variant patterns and orchestration
  • gesture_guide.md
    - Comprehensive gesture handling guide
scripts/
  • animation_generator.py
    - Generate Motion component boilerplate
  • variant_builder.py
    - Interactive variant configuration tool
assets/
  • starter_motion/
    - Complete Motion + Vite starter template
  • examples/
    - Real-world Motion component patterns
本技能包含:
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组件模式

Community Resources

社区资源