motion入门教程

43 阅读6分钟

一.使用背景

在 React 项目中,如果要做动画,Motion 是一个非常常用的库。

它常见的用途有:

  • 入场动画

  • 悬停动画

  • 点击反馈

  • 拖拽动画

  • 退出动画

  • 滚动联动

所以只要是 React 开发,基本都会接触到它。

二.如何使用

1.安装:

npm install motion

2.导入:

import { motion } from "motion/react"

3.使用:
在基本的html元素前面加上motion就能开启动画

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 5 }}
>
  Hello Framer Motion!
</motion.div>

例如上面这个:在div前面加上motion之后,即可开始使用motion里面的属性,比如initial/animate/transition等之类的属性


三、导入更多功能

import { motion } from "motion/react"

如果要用更多功能,也可以这样导入:

import {
  motion,
  AnimatePresence,
  useMotionValue,
  useTransform,
  useMotionValueEvent,
  useSpring,
  useScroll
} from "motion/react"

四、第一步:先会最基础的动画

这一部分先记住 3 个属性:

  • initial
  • animate
  • transition

它们分别表示:

  • initial:开始状态
  • animate:结束状态
  • transition:过渡方式

1. 最小例子

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 1 }}
>
  Hello Motion
</motion.div>


2. 常见写法

透明度

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
/>

从左边进入

<motion.div
  initial={{ x: -100 }}
  animate={{ x: 0 }}
/>

从下方进入

<motion.div
  initial={{ y: 50 }}
  animate={{ y: 0 }}
/>

缩放进入

<motion.div
  initial={{ scale: 0.5 }}
  animate={{ scale: 1 }}
/>

旋转进入

<motion.div
  initial={{ rotate: -45 }}
  animate={{ rotate: 0 }}
/>

3. 一个稍微完整一点的例子

<motion.div
  initial={{ opacity: 0, y: 50, scale: 0.8 }}
  animate={{ opacity: 1, y: 0, scale: 1 }}
  transition={{ duration: 0.8 }}
>
  入场动画
</motion.div>


五、第二步:鼠标交互动画

这一部分最常用的是:

  • whileHover
  • whileTap

1. whileHover

鼠标移入时触发。

<motion.button
  whileHover={{ scale: 1.1 }}
>
  鼠标移入试试
</motion.button>


2. whileTap

鼠标按下时触发。

<motion.button
  whileTap={{ scale: 0.9 }}
>
  点击试试
</motion.button>


3. 两个一起写

<motion.button
  whileHover={{ scale: 1.1 }}
  whileTap={{ scale: 0.95 }}
>
  按钮
</motion.button>


4. 卡片悬停上浮

<motion.div
  whileHover={{ y: -10 }}
>
  卡片内容
</motion.div>


六、第三步:退出动画

如果一个元素是条件渲染的:

{show && <div>内容</div>}

那么它消失时会直接没掉。
如果想让它“离开前先播放动画”,就需要:

  • AnimatePresence
  • exit

1. 最小例子

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

function Demo() {
  const [show, setShow] = useState(true)

  return (
    <div>
      <button onClick={() => setShow(!show)}>切换</button>

      <AnimatePresence>
        {show && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            这是一段内容
          </motion.div>

        )}
      </AnimatePresence>

    </div>

  )
}

2. 退出时顺便向上移动

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: -20 }}
>
  内容
</motion.div>


3. 退出时向右飞走

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0, x: 200 }}
>
  内容
</motion.div>


七、第四步:拖拽

拖拽常见于:

  • 卡片
  • 面板
  • 滑块
  • 图片查看器

1. 最简单拖拽

<motion.div drag>
  拖我
</motion.div>


2. 只允许左右拖

<motion.div drag="x">
  左右拖
</motion.div>


3. 只允许上下拖

<motion.div drag="y">
  上下拖
</motion.div>


4. 限制拖拽范围

<motion.div
  drag="x"
  dragConstraints={{ left: -100, right: 100 }}
>
  只能在范围内拖
</motion.div>


5. 拖拽结束时执行逻辑

<motion.div
  drag="x"
  onDragEnd={() => {
    console.log("拖拽结束")
  }}
>
  拖我
</motion.div>


八、第五步:useMotionValue

如果你想拿到拖动过程中的值,就要学这个。


1. 创建一个动态值

import { useMotionValue } from "motion/react"

const x = useMotionValue(0)

2. 绑定到元素上

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

function Demo() {
  const x = useMotionValue(0)

  return (
    <motion.div
      drag="x"
      style={{ x }}
    >
      拖我
    </motion.div>

  )
}

3. 获取当前值

const currentX = x.get()

4. 手动设置值

x.set(100)

5. 拖拽结束时判断距离

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

function Demo() {
  const x = useMotionValue(0)

  return (
    <motion.div
      drag="x"
      style={{ x }}
      onDragEnd={() => {
        const currentX = x.get()
        if (Math.abs(currentX) > 100) {
          console.log("拖得够远")
        } else {
          console.log("拖得不够远")
        }
      }}
    >
      拖我
    </motion.div>

  )
}

九、第六步:useTransform

useTransform 是把一个值映射成另一个值。
最常见的是:拖动一个值,同时带动别的效果变化。


1. 拖动时旋转

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

function Demo() {
  const x = useMotionValue(0)
  const rotate = useTransform(x, [-200, 0, 200], [-18, 0, 18])

  return (
    <motion.div
      drag="x"
      style={{ x, rotate }}
    >
      拖我会旋转
    </motion.div>

  )
}

2. 拖动时透明度变化

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

function Demo() {
  const x = useMotionValue(0)
  const opacity = useTransform(x, [-200, 0, 200], [0.4, 1, 0.4])

  return (
    <motion.div
      drag="x"
      style={{ x, opacity }}
    >
      拖我会变透明
    </motion.div>

  )
}

3. 拖动时缩放变化

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

function Demo() {
  const x = useMotionValue(0)
  const scale = useTransform(x, [-200, 0, 200], [0.9, 1, 0.9])

  return (
    <motion.div
      drag="x"
      style={{ x, scale }}
    >
      拖我会缩放
    </motion.div>

  )
}

4. 一个值联动多个效果

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

function Demo() {
  const x = useMotionValue(0)
  const rotate = useTransform(x, [-200, 0, 200], [-18, 0, 18])
  const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5])
  const scale = useTransform(x, [-200, 0, 200], [0.9, 1, 0.9])

  return (
    <motion.div
      drag="x"
      style={{ x, rotate, opacity, scale }}
    >
      多效果联动
    </motion.div>

  )
}

十、第七步:useMotionValueEvent

这个是监听 motion 值变化。


1. 最小例子

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

function Demo() {
  const x = useMotionValue(0)

  useMotionValueEvent(x, "change", (latest) => {
    console.log(latest)
  })

  return (
    <motion.div
      drag="x"
      style={{ x }}
    >
      拖我
    </motion.div>

  )
}

2. 根据拖拽方向改提示文字

import { useState } from "react"
import { motion, useMotionValue, useMotionValueEvent } from "motion/react"

function Demo() {
  const [text, setText] = useState("还没开始拖动")
  const x = useMotionValue(0)

  useMotionValueEvent(x, "change", (latest) => {
    if (latest > 10) {
      setText("正在往右拖")
    } else if (latest < -10) {
      setText("正在往左拖")
    } else {
      setText("回到中间")
    }
  })

  return (
    <div>
      <p>{text}</p>

      <motion.div
        drag="x"
        style={{ x }}
      >
        拖我
      </motion.div>

    </div>

  )
}

十一、第八步:useSpring

这个是让值的变化更平滑。


1. 最简单用法

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

function Demo() {
  const x = useMotionValue(0)
  const smoothX = useSpring(x)

  return (
    <motion.div
      drag="x"
      style={{ x: smoothX }}
    >
      丝滑拖动
    </motion.div>

  )
}

2. 自定义弹簧参数

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

function Demo() {
  const x = useMotionValue(0)
  const smoothX = useSpring(x, {
    stiffness: 120,
    damping: 20
  })

  return (
    <motion.div
      drag="x"
      style={{ x: smoothX }}
    >
      自定义弹簧效果
    </motion.div>

  )
}

十二、第九步:useScroll

这个是监听滚动值。

常见用途:

  • 顶部阅读进度条
  • 滚动联动动画
  • 滚动时显示/隐藏元素

1. 顶部进度条

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

function Demo() {
  const { scrollYProgress } = useScroll()

  return (
    <motion.div
      style={{
        scaleX: scrollYProgress,
        position: "fixed",
        top: 0,
        left: 0,
        right: 0,
        height: 4,
        background: "red",
        transformOrigin: "0%"
      }}
    />
  )
}

2. 打印当前滚动距离

import { useScroll, useMotionValueEvent } from "motion/react"

function Demo() {
  const { scrollY } = useScroll()

  useMotionValueEvent(scrollY, "change", (latest) => {
    console.log("当前滚动距离:", latest)
  })

  return <div style={{ height: "200vh" }}>滚动页面试试</div>

}

十三、第十步:综合例子

这个例子把前面的内容串起来:

  • 拖拽
  • 动态值
  • 值映射
  • 值监听
  • 退出动画
import { useState } from "react"
import {
  motion,
  AnimatePresence,
  useMotionValue,
  useTransform,
  useMotionValueEvent
} from "motion/react"

function Demo() {
  const [show, setShow] = useState(true)
  const [text, setText] = useState("还没开始拖动")

  const x = useMotionValue(0)
  const rotate = useTransform(x, [-150, 0, 150], [-15, 0, 15])
  const opacity = useTransform(x, [-150, 0, 150], [0.5, 1, 0.5])

  useMotionValueEvent(x, "change", (latest) => {
    if (latest > 10) {
      setText("正在往右拖")
    } else if (latest < -10) {
      setText("正在往左拖")
    } else {
      setText("回到中间")
    }
  })

  return (
    <div>
      <p>{text}</p>

      <AnimatePresence>
        {show && (
          <motion.div
            drag="x"
            dragConstraints={{ left: 0, right: 0 }}
            style={{ x, rotate, opacity }}
            onDragEnd={() => {
              if (Math.abs(x.get()) > 100) {
                setShow(false)
              }
            }}
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, x: 300, rotate: 20 }}
            transition={{ type: "spring", stiffness: 120, damping: 15 }}
          >
            拖我删除
          </motion.div>

        )}
      </AnimatePresence>

    </div>

  )
}

十四、建议学习顺序

我自己觉得 Motion 比较适合按这个顺序学:

第一步:基础动画

  • motion.div
  • initial
  • animate
  • transition

第二步:交互动画

  • whileHover
  • whileTap

第三步:退出动画

  • AnimatePresence
  • exit

第四步:拖拽

  • drag
  • dragConstraints
  • onDragEnd

第五步:动态值

  • useMotionValue
  • useTransform
  • useMotionValueEvent
  • useSpring

第六步:滚动

  • useScroll

十五、常见坑点

1. 忘了导入 motion

import { motion } from "motion/react"

2. 普通标签不能直接写动画属性

错误写法:

<div initial={{ opacity: 0 }}></div>

正确写法:

<motion.div initial={{ opacity: 0 }}></motion.div>


3. exit 不生效

一般是因为没有包:

<AnimatePresence>

4. useMotionValueEvent 监听错对象

正确写法:

const x = useMotionValue(0)

useMotionValueEvent(x, "change", (latest) => {
  console.log(latest)
})

5. 拖拽结束后位置没有重置

有些场景下要手动重置:

x.set(0)

6. 拖拽判断太敏感

不建议直接这样写:

if (latest > 0)

更适合写成:

if (latest > 10)

这样会更稳定一些。


十六、总结

Motion 是 React 中非常重要的动画库。

它能做:

  • 入场动画
  • 悬停动画
  • 点击动画
  • 拖拽动画
  • 删除退场动画
  • 滚动联动动画

学习时建议按顺序来,不要一下子全学完:

  1. 基础动画
  2. 交互动画
  3. 退出动画
  4. 拖拽
  5. 动态值
  6. 滚动

这样会更清晰,也更容易记住。


十七、demo 地址

地址:gitee.com/rui-rui-an/…

十八、更加详细的教学地址

地址:www.yuque.com/qingzhouyou…