Framer Motion:React 里几行写出生产级动画

CSS transition / keyframes 能解决简单动画,复杂的(列表重排、组件进入/离开、
gesture、SVG morph)就力不从心。Framer Motion 是 React 生态的事实标准动画库,
API 简洁,自动 GPU 加速。

安装

npm i framer-motion

1. 最简单的进入动画

import { motion } from 'framer-motion'

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.4 }}
>
  Hello
</motion.div>

任何 HTML 元素加 motion. 前缀即可。

2. 进入 + 离开(AnimatePresence)

import { motion, AnimatePresence } from 'framer-motion'

<AnimatePresence>
  {visible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.9 }}
    >
      Modal
    </motion.div>
  )}
</AnimatePresence>

AnimatePresence 在子元素被 React 卸载时延迟卸载,让 exit 动画完成。
必须给每个直接子元素显式 key

3. 列表动画(重排 / 添加 / 删除)

<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      layout                     // 自动 FLIP 动画
      initial={{ opacity: 0, x: -20 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 20 }}
    >
      {item.text}
    </motion.li>
  ))}
</AnimatePresence>

layout 让元素位置变化时自动 morph(FLIP 算法)。
增删 / 排序列表时无需手动算位移。

4. variants:复用动画状态

const variants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
}

<motion.div variants={variants} initial="hidden" animate="visible">
  ...
</motion.div>

// 父子联动
const parent = {
  hidden: { opacity: 0 },
  visible: { opacity: 1, transition: { staggerChildren: 0.1 } },
}
const child = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
}

<motion.ul variants={parent} initial="hidden" animate="visible">
  {items.map(i => <motion.li key={i.id} variants={child}>{i.text}</motion.li>)}
</motion.ul>

staggerChildren: 0.1 让子元素一个接一个延迟 0.1s 进入,列表波浪效果。

5. 手势:hover / tap / drag

<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
>
  Click
</motion.button>

<motion.div
  drag
  dragConstraints={{ left: -100, right: 100, top: 0, bottom: 0 }}
  dragElastic={0.5}
  whileDrag={{ scale: 1.1 }}
>
  拖我
</motion.div>

drag 自动处理触摸 / 鼠标拖动。dragConstraints 限制范围。

6. transition 选项

<motion.div
  animate={{ x: 100 }}
  transition={{
    duration: 0.5,
    ease: 'easeOut',   // 'linear' | 'easeIn' | 'easeInOut' | 'circIn' | ...
    // 或弹簧物理
    type: 'spring',
    stiffness: 100,
    damping: 10,
  }}
/>

弹簧(spring)参数比 duration 更自然,是 Framer Motion 默认。

7. scroll-triggered 动画

import { motion, useScroll, useTransform } from 'framer-motion'

function Hero() {
  const { scrollYProgress } = useScroll()
  const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0])
  const y = useTransform(scrollYProgress, [0, 1], [0, -200])
  return <motion.h1 style={{ opacity, y }}>Title</motion.h1>
}

useTransform 把 0-1 的 scrollYProgress 映射到任意值范围,做视差效果。

8. layout 高级:跨组件共享

import { motion } from 'framer-motion'

// 小卡片
<motion.div layoutId="card-1" onClick={open}>
  <img src="thumb.jpg" />
</motion.div>

// 弹出全屏
{open && (
  <motion.div layoutId="card-1" className="fullscreen">
    <img src="full.jpg" />
  </motion.div>
)}

两个不同位置的元素 layoutId 相同 → Framer 自动 morph 从一个位置到另一个。
"卡片展开"效果一行写完。

9. SVG morph

<motion.path
  d={pathData}
  initial={{ pathLength: 0 }}
  animate={{ pathLength: 1 }}
  transition={{ duration: 2 }}
/>

pathLength: 0 → 1 让 SVG 路径"画出来"效果。

10. 性能注意

Framer Motion 默认用 transform / opacity(GPU 友好)。不要动 width / height /
top / left(CPU 重排)。

// ❌ width 变化触发 layout
<motion.div animate={{ width: 200 }} />

// ✅ transform scale
<motion.div animate={{ scaleX: 2 }} />

11. 与 Tailwind / CSS-in-JS

motion.div 接受所有 HTML props,class / style 正常写:

<motion.div
  className="bg-blue-500 rounded p-4"
  whileHover={{ scale: 1.05 }}
/>

12. 替代方案

  • react-spring:物理为主,API 不同
  • gsap:传统 JS 动画,强大但非 React-first
  • Motion One:framer-motion 作者的"无 React 依赖"版

React 项目就用 Framer Motion,最省事。

踩过的坑

  • AnimatePresence 子元素必须有 key:忘了 key 看不到 exit 动画。
  • 直接给 <div> 加 motion 属性不工作:必须用 motion.div
  • 大量元素同时 layout 动画 → 卡。layout 只给真正需要 morph 的元素加。
  • prefers-reduced-motion:尊重用户系统设置,给敏感动画加:
    tsx const reduce = useReducedMotion() <motion.div animate={{ x: reduce ? 0 : 100 }} />
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。