一.使用背景
在 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 个属性:
initialanimatetransition
它们分别表示:
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>
五、第二步:鼠标交互动画
这一部分最常用的是:
whileHoverwhileTap
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>}
那么它消失时会直接没掉。
如果想让它“离开前先播放动画”,就需要:
AnimatePresenceexit
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.divinitialanimatetransition
第二步:交互动画
whileHoverwhileTap
第三步:退出动画
AnimatePresenceexit
第四步:拖拽
dragdragConstraintsonDragEnd
第五步:动态值
useMotionValueuseTransformuseMotionValueEventuseSpring
第六步:滚动
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 中非常重要的动画库。
它能做:
- 入场动画
- 悬停动画
- 点击动画
- 拖拽动画
- 删除退场动画
- 滚动联动动画
学习时建议按顺序来,不要一下子全学完:
- 基础动画
- 交互动画
- 退出动画
- 拖拽
- 动态值
- 滚动
这样会更清晰,也更容易记住。