MotionReact前端动画Next.js

Motion for React 完整指南:动画、手势与退出效果

从安装和 motion 组件开始,系统掌握 Motion for React 的 variants、拖拽手势、MotionValue、useTransform 与 AnimatePresence,并了解 Next.js 使用方式和性能边界。

·更新于 ·阅读约 14 分钟·计算中...
Motion for React 完整指南:动画、手势与退出效果

Motion for React 适合处理与组件状态、布局变化和用户手势紧密相关的动画。普通的颜色渐变、悬停效果优先使用 CSS;当动画需要跟随 React 状态、支持退出过程、拖拽或多个元素编排时,再使用 Motion。

本文合并了本站原有的 Motion 入门、motion.div、variants、拖拽、useMotionValueuseTransformAnimatePresence 系列,示例采用当前官方文档中的 motion/react 导入方式。

目录

安装与导入方式

新项目安装 motion

npm install motion

在普通 React 客户端组件中导入:

import { motion } from "motion/react"

在 Next.js App Router 中,包含交互状态和 Hooks 的动画组件需要声明为客户端组件:

"use client"

import { motion } from "motion/react"

export function WelcomeCard() {
  return (
    <motion.article
      initial={{ opacity: 0, y: 16 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.35, ease: "easeOut" }}
    >
      欢迎使用 FreeMac
    </motion.article>
  )
}

旧项目仍可能使用 framer-motion 包。不要在同一个项目中混用两套导入路径;升级时先根据 Motion 官方升级与 React 文档 检查现有 API。

motion 组件的三个基本状态

motion.divmotion.button 等组件保留原生元素的属性,同时增加动画能力:

  • initial:元素首次出现时的状态。
  • animate:当前目标状态。
  • transition:持续时间、缓动和弹簧参数。
  • exit:元素离开 React 树时的状态,需要配合 AnimatePresence
<motion.button
  initial={{ scale: 0.96, opacity: 0 }}
  animate={{ scale: 1, opacity: 1 }}
  whileHover={{ scale: 1.03 }}
  whileTap={{ scale: 0.98 }}
  transition={{ type: "spring", stiffness: 320, damping: 24 }}
>
  立即开始
</motion.button>

动画两端尽量使用相同值类型,例如从 0px100px。位移和缩放优先使用 xyscalerotate,通常比频繁改变 topleft 或大面积阴影更平滑。

用 variants 管理多个元素

当父子元素共享“隐藏/显示”等语义状态时,用 variants 比在每个元素上重复写对象更清楚:

const list = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.08 },
  },
}

const item = {
  hidden: { opacity: 0, y: 12 },
  visible: { opacity: 1, y: 0 },
}

export function ToolList({ tools }: { tools: string[] }) {
  return (
    <motion.ul variants={list} initial="hidden" animate="visible">
      {tools.map((tool) => (
        <motion.li key={tool} variants={item}>
          {tool}
        </motion.li>
      ))}
    </motion.ul>
  )
}

staggerChildren 适合列表依次出现;如果只是单个元素变化,直接使用 animate 会更易读。

悬停、点击与拖拽手势

常见交互由 whileHoverwhileTapdrag 提供:

<motion.div
  drag="x"
  dragConstraints={{ left: -120, right: 120 }}
  dragElastic={0.15}
  whileDrag={{ scale: 1.04, cursor: "grabbing" }}
  style={{ cursor: "grab" }}
>
  左右拖动
</motion.div>

拖拽元素要有明确边界。移动端还要检查滚动手势是否冲突,并避免让动画成为完成核心任务的唯一方式。

useMotionValue 与 useTransform

MotionValue 用于高频变化的动画值。它可以直接更新样式,而不必让 React 在每一帧重新渲染组件。useTransform 则把一个 MotionValue 的输入范围映射为新的输出值。

"use client"

import { motion, useMotionValue, useTransform } from "motion/react"

export function SwipeCard() {
  const x = useMotionValue(0)
  const rotate = useTransform(x, [-200, 0, 200], [-12, 0, 12])
  const opacity = useTransform(x, [-200, 0, 200], [0.35, 1, 0.35])

  return (
    <motion.article
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      style={{ x, rotate, opacity }}
    >
      拖动卡片
    </motion.article>
  )
}

Motion 官方说明,useTransform 既能映射数值、颜色等范围,也能通过转换函数组合多个 MotionValue。只有当数值确实高频变化或需要连续映射时才使用它;普通开关状态继续使用 React state。

AnimatePresence:让元素退出时也有动画

React 条件渲染会立即移除元素。AnimatePresence 会让带有 exit 的元素在离开前完成动画:

"use client"

import { AnimatePresence, motion } from "motion/react"

export function Modal({ open }: { open: boolean }) {
  return (
    <AnimatePresence>
      {open && (
        <motion.div
          key="modal"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          <motion.section
            initial={{ scale: 0.96, y: 16 }}
            animate={{ scale: 1, y: 0 }}
            exit={{ scale: 0.96, y: 12 }}
          >
            弹窗内容
          </motion.section>
        </motion.div>
      )}
    </AnimatePresence>
  )
}

直接子元素必须有稳定且唯一的 key。页面切换需要“先退场、后入场”时,可以使用 mode="wait";列表布局变化更适合结合 layoutmode="popLayout"。具体限制以 AnimatePresence 官方文档 为准。

性能与可访问性检查

上线前至少检查以下项目:

  1. 优先动画 transformopacity,避免持续触发布局计算。
  2. 不要给几十个不可见元素同时启动无限动画。
  3. 弹簧动画出现抖动时,调整 stiffnessdamping,而不是叠加多个 transition。
  4. 使用稳定的列表 key,避免元素被误判为全新节点。
  5. 尊重用户的“减少动态效果”设置,对大幅位移提供淡入或无动画替代。
  6. 在低性能设备上实际测试拖拽、滚动和页面切换。

如何选择实现方式

场景 推荐方式
简单悬停、颜色和透明度变化 CSS transition
动画跟随 React 状态 motion 组件
列表依次出现 variants + staggerChildren
拖拽、滚动映射、高频数值 MotionValue + useTransform
弹窗、路由或列表退出 AnimatePresence
很长的时间轴或复杂画布动画 评估 GSAP、Canvas 等专用方案

Motion 负责动画表达,React 仍负责状态来源。对 Hooks 的职责还不确定时,可以继续阅读本站的 React Hooks 完整指南

参考资料

订阅 FreeMac

每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。