10个Framer Motion动画案例,让你的网站活起来
关键词:Framer Motion、网页动画、交互设计、React库、用户体验、入场动画、悬停效果
摘要:在这个“颜值即正义”的互联网时代,网页动画早已从“加分项”变成“必选项”。Framer Motion作为React生态中最受欢迎的动画库,用简单代码就能实现丝滑交互。本文精选10个高频实用的动画案例,从页面加载到用户点击,覆盖90%常见场景,手把手教你用Framer Motion让网站“活起来”!
背景介绍
目的和范围
本文旨在帮助前端开发者快速掌握Framer Motion的核心用法,通过10个具体案例覆盖网页动画的主流场景。无论你是刚接触动画的新手,还是想优化现有交互的进阶者,都能找到可直接复用的解决方案。
预期读者
- 熟悉React基础的前端开发者
- 对网页动画感兴趣的UI/UX设计师
- 想提升网站用户体验的独立开发者
文档结构概述
本文先通过“会动的积木”故事引入Framer Motion核心概念,再逐一解析10个经典案例(含代码+原理),最后总结实战技巧与未来趋势。
术语表(用“魔法道具”比喻理解)
术语 | 比喻解释 |
---|---|
motion组件 | 会动的积木(如motion.div是会跳舞的盒子,motion.img是会翻转的图片) |
initial | 动画起点(积木的“初始姿势”) |
animate | 动画终点(积木要到达的“最终姿势”) |
whileHover | 鼠标悬停触发器(相当于“摸一下积木就开始跳舞”的魔法) |
transition | 动画节奏控制器(决定积木“慢慢走”还是“蹦蹦跳”到终点) |
AnimatePresence | 元素离场监视器(当积木被“收进盒子”时,自动播放离场动画) |
核心概念与联系:用“魔法积木”理解Framer Motion
故事引入:魔法积木屋的秘密
想象你有一间积木屋,里面的每块积木都会“听指令”跳舞:
- 当你推开门(页面加载),积木从地面“飘”到桌面(入场动画);
- 你摸一下红色积木(鼠标悬停),它会“蹦”起来再落回去(悬停动画);
- 当你要收走蓝色积木(元素卸载),它会先转个圈再消失(离场动画)。
Framer Motion就像给积木屋的每块积木施了魔法,你只需要用简单的“咒语”(代码)告诉它们“从哪来”“到哪去”“怎么动”,剩下的魔法由它自动完成!
核心概念解释(像给小朋友讲魔法)
1. motion组件:会动的积木块
Framer Motion提供了一系列“会动”的HTML标签,比如motion.div
(会动的盒子)、motion.button
(会动的按钮)、motion.img
(会动的图片)。它们和普通HTML标签用法一样,但多了控制动画的“魔法属性”。
类比:普通积木是木头做的,motion积木是“魔法木头”做的,能听懂“动起来”的指令。
2. initial & animate:动画的起点和终点
initial
定义动画开始时的状态(比如透明度0、位置偏移),animate
定义动画结束时的状态(透明度1、位置归位)。就像告诉魔法积木:“你先站在墙角(initial),然后走到桌子中间(animate)”。
类比:小朋友玩“木头人”游戏,initial是“准备姿势”,animate是“最终姿势”。
3. transition:动画的“性格”
transition
控制动画的时长、缓动函数(ease)、延迟等,决定动画是“慢悠悠”还是“急匆匆”。比如设置type: "spring"
(弹簧效果),动画会像弹簧一样有弹性。
类比:同样从A到B,有人走得慢(easeOut),有人跑两步跳一下(spring)。
4. whileHover/whileTap:触发魔法的开关
whileHover
是鼠标悬停时触发动画,whileTap
是手指点击(或鼠标点击)时触发。就像给积木装了“传感器”,只要摸一下(悬停)或按一下(点击),它就开始跳舞。
类比:小区的声控灯,发出声音(悬停/点击)就会亮(触发动画)。
核心概念关系:魔法积木的协作流程
当你想让一个按钮做“悬停弹跳”动画时,四个核心概念会这样合作:
motion.button
(会动的按钮积木)负责接收指令;initial
告诉它“初始姿势”(比如scale:1,不放大);whileHover
触发“悬停时的姿势”(比如scale:1.1,放大10%);transition
规定“从初始到悬停”的变化节奏(比如duration:0.2,0.2秒完成,type:spring有弹性)。
就像指挥一个小朋友:“你站在这(initial),当有人摸你(whileHover),你就跳起来(scale:1.1),跳的时候要像弹簧一样有弹性(transition:spring)。”
核心原理示意图(专业版)
用户交互(悬停/点击) → 触发whileHover/whileTap → 计算initial到目标状态的差值 → transition控制插值过程 → 浏览器通过GPU加速渲染动画帧
Mermaid 流程图
graph TD
A[用户操作] --> B{触发条件}
B -->|悬停| C[whileHover]
B -->|点击| D[whileTap]
B -->|加载/卸载| E[initial/animate]
C --> F[计算起始状态(initial)]
D --> F
E --> F
F --> G[transition控制节奏]
G --> H[GPU加速渲染]
H --> I[用户看到动画]
10个实战案例:手把手教你写会动的代码
案例1:页面加载入场动画(淡入+缩放)
效果描述:页面加载时,标题从“透明+缩小”渐变为“不透明+正常大小”,给用户“内容正在出现”的提示。
代码实现:
import { motion } from "framer-motion";
function HeroTitle() {
return (
<motion.h1
initial={{ opacity: 0, scale: 0.8 }} // 初始状态:透明+缩小20%
animate={{ opacity: 1, scale: 1 }} // 最终状态:不透明+正常大小
transition={{ duration: 0.5 }} // 动画时长0.5秒,默认缓动easeInOut
>
欢迎来到魔法积木屋!
</motion.h1>
);
}
关键参数解析:
initial
:动画起点(未加载时的“隐藏状态”);animate
:动画终点(加载完成后的“显示状态”);transition.duration
:动画总时间,0.5秒足够自然。
应用场景:首屏标题、卡片列表、导航栏等需要“渐入”的元素。
案例2:悬停弹跳按钮(弹簧效果)
效果描述:鼠标悬停在按钮上时,按钮像弹簧一样“弹起”10%,鼠标移开后“弹回”,增加交互趣味性。
代码实现:
<motion.button
whileHover={{ scale: 1.1 }} // 悬停时放大10%
whileTap={{ scale: 0.95 }} // 点击时略微缩小(模拟按压感)
transition={{
type: "spring", // 弹簧类型,更有弹性
stiffness: 300, // 弹簧“硬度”,值越大回弹越快
damping: 20 // 阻尼,值越大晃动越少
}}
style={{ padding: "10px 20px" }}
>
点击领取魔法
</motion.button>
关键参数解析:
type: "spring"
:相比线性动画,弹簧效果更符合物理直觉;stiffness
:想象成弹簧的“松紧”,300属于中等硬度,不会太“软”或太“硬”;damping
:控制弹簧的“晃动次数”,20能让动画快速停止,避免晃太久。
应用场景:主要操作按钮(如“立即购买”“提交表单”)、交互图标(如爱心点赞)。
案例3:卡片翻转交互(鼠标移入)
效果描述:鼠标悬停在卡片上时,卡片沿Y轴旋转180度,展示背面内容(如价格/详情),移开后旋转回来。
代码实现:
const Card = () => {
return (
<motion.div
whileHover={{ rotateY: 180 }} // 悬停时Y轴旋转180度
transition={{ duration: 0.6 }}
style={{
width: 200,
height: 300,
backgroundColor: "#fff",
borderRadius: 10,
display: "flex",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
transformStyle: "preserve-3d" // 关键!保留3D变换空间
}}
>
{/* 正面内容 */}
<motion.div
initial={{ opacity: 1 }}
animate={{ opacity: whileHover ? 0 : 1 }} // 悬停时正面消失
style={{ position: "absolute", backfaceVisibility: "hidden" }} // 背面不可见
>
正面:点击查看详情
</motion.div>
{/* 背面内容 */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: whileHover ? 1 : 0 }} // 悬停时背面显示
style={{ position: "absolute", transform: "rotateY(180deg)", backfaceVisibility: "hidden" }}
>
背面:价格99元
</motion.div>
</motion.div>
);
};
关键技巧:
transformStyle: "preserve-3d"
:让子元素继承3D变换空间,否则翻转时背面会“贴”在卡片上;backfaceVisibility: "hidden"
:隐藏元素背面(避免翻转时看到正面的“倒影”);- 通过
opacity
动画控制正背面内容的显隐,与旋转同步。
应用场景:产品展示卡片、信息卡片(如简历、项目介绍)。
案例4:滚动触发的渐显动画(ScrollTrigger)
效果描述:当用户滚动到某个元素(如服务列表)时,元素从底部“滑入”并渐显,引导用户注意力。
代码实现(需配合useScroll
钩子):
import { motion, useScroll } from "framer-motion";
function ServiceCard() {
const { scrollYProgress } = useScroll(); // 获取滚动进度(0-1)
return (
<motion.div
// 根据滚动进度计算偏移量:当进度>0.2时开始动画
transform={{
y: scrollYProgress > 0.2 ? 0 : 50, // 未滚动时Y偏移50px(下方)
opacity: scrollYProgress > 0.2 ? 1 : 0
}}
transition={{ duration: 0.5 }}
style={{ margin: "20px 0", padding: 20, backgroundColor: "#f0f0f0" }}
>
专业魔法教学服务
</motion.div>
);
}
进阶技巧:
useScroll
钩子需要包裹在ScrollContainer
组件中(通常是页面根容器);- 可以通过
start
和end
参数自定义触发位置(如start: "top center"
表示元素顶部到达视口中心时触发)。
应用场景:长页面中的分段内容(如About Us、服务列表、团队介绍)。
案例5:导航菜单展开/收起(高度过渡)
效果描述:点击“菜单”按钮时,隐藏的导航列表从高度0平滑展开到实际高度,关闭时收缩回去。
代码实现(需配合useState
和AnimatePresence
):
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
function MobileNav() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>菜单</button>
<AnimatePresence> {/* 监视子元素的挂载/卸载 */}
{isOpen && (
<motion.ul
key="nav-menu"
initial={{ height: 0 }} // 初始高度0(隐藏)
animate={{ height: "auto" }} // 最终高度自动(展开)
exit={{ height: 0 }} // 卸载时收缩回0
transition={{ duration: 0.3 }}
style={{
listStyle: "none",
padding: 0,
margin: "10px 0",
backgroundColor: "#fff"
}}
>
<li>首页</li>
<li>产品</li>
<li>关于</li>
</motion.ul>
)}
</AnimatePresence>
</div>
);
}
关键组件:
AnimatePresence
:必须包裹在需要“入场/离场”动画的元素外,它会检测子元素的key
变化,触发exit
动画;height: "auto"
:自动计算展开后的高度,无需手动设置具体数值(更灵活)。
应用场景:移动端导航菜单、折叠面板(Accordion)、下拉选择框。
案例6:图片轮播滑动切换(滑动手势)
效果描述:用户左右滑动图片时,当前图片向左滑出,下一张图片从右侧滑入,模拟原生App的轮播体验。
代码实现(简化版):
import { motion, useDrag } from "framer-motion";
function ImageCarousel() {
const [currentIndex, setCurrentIndex] = useState(0);
const images = ["img1.jpg", "img2.jpg", "img3.jpg"];
const dragControls = useDrag(({ offset }) => {
// 根据拖拽偏移量判断是否切换图片(简化逻辑)
if (offset.x > 100) setCurrentIndex(Math.max(0, currentIndex - 1)); // 右滑(上一张)
if (offset.x < -100) setCurrentIndex(Math.min(2, currentIndex + 1)); // 左滑(下一张)
});
return (
<div style={{ overflow: "hidden", width: "100%" }}>
{images.map((img, index) => (
<motion.img
key={img}
src={img}
style={{
width: "100%",
position: "absolute",
top: 0,
left: 0
}}
initial={{ x: index === currentIndex ? 0 : "100%" }} // 当前图居中,其他在右侧
animate={{ x: index === currentIndex ? 0 : "100%" }}
transition={{ type: "spring", stiffness: 150 }}
{...dragControls} // 绑定拖拽事件
/>
))}
</div>
);
}
关键技巧:
useDrag
钩子:监听用户拖拽操作,获取偏移量(offset.x);- 图片定位为
absolute
:所有图片叠在一起,通过x
位移控制显示哪一张; - 弹簧过渡(
type: "spring"
):模拟手指松开后的“回弹”效果。
应用场景:产品图轮播、广告位展示、摄影作品画廊。
案例7:表单输入焦点动画(输入框扩展)
效果描述:当用户点击输入框时,输入框宽度从80%扩展到100%,边框颜色变深,提升输入时的沉浸感。
代码实现:
<motion.input
initial={{ width: "80%", borderColor: "#ddd" }}
whileFocus={{ width: "100%", borderColor: "#2196F3" }} // 聚焦时扩展+变色
transition={{ duration: 0.3 }}
placeholder="输入你的魔法咒语..."
style={{
padding: "10px",
border: "2px solid",
borderRadius: 5,
outline: "none"
}}
/>
细节优化:
- 同时动画
width
和borderColor
:多属性同步变化更自然; outline: "none"
:移除浏览器默认聚焦框,用自定义动画替代。
应用场景:登录/注册表单、搜索框、评论输入框。
案例8:购物车添加抛物线动画(商品“飞”入购物车)
效果描述:用户点击“加入购物车”按钮时,商品图片从按钮位置“飞”到页面右上角的购物车图标,模拟“添加成功”的反馈。
代码实现(需计算坐标):
import { motion, useRef, useSpring } from "framer-motion";
function AddToCartButton() {
const [isAnimating, setIsAnimating] = useState(false);
const buttonRef = useRef(null); // 按钮DOM引用
const cartRef = useRef(null); // 购物车DOM引用
const spring = useSpring(); // 弹簧动画控制器
const handleAddToCart = () => {
setIsAnimating(true);
// 获取按钮和购物车的坐标
const buttonRect = buttonRef.current.getBoundingClientRect();
const cartRect = cartRef.current.getBoundingClientRect();
// 计算偏移量(从按钮中心到购物车中心)
const x = cartRect.left - buttonRect.left + (cartRect.width/2 - buttonRect.width/2);
const y = cartRect.top - buttonRect.top + (cartRect.height/2 - buttonRect.height/2);
// 触发弹簧动画
spring.start({ x, y, onComplete: () => setIsAnimating(false) });
};
return (
<div>
{/* 购物车图标 */}
<div ref={cartRef} style={{ position: "fixed", top: 20, right: 20 }}>🛒</div>
{/* 添加按钮 */}
<button ref={buttonRef} onClick={handleAddToCart}>加入购物车</button>
{/* 动画元素(商品图片) */}
{isAnimating && (
<motion.img
src="product.jpg"
style={{
position: "fixed",
left: buttonRef.current?.getBoundingClientRect().left,
top: buttonRef.current?.getBoundingClientRect().top,
width: 50,
height: 50,
pointerEvents: "none"
}}
animate={{ x: spring.x, y: spring.y }}
transition={{ type: "spring", mass: 0.5, damping: 10 }}
onComplete={() => setIsAnimating(false)}
/>
)}
</div>
);
}
关键步骤:
- 使用
getBoundingClientRect()
获取按钮和购物车的位置; - 计算动画元素的起始(按钮位置)和结束(购物车位置)坐标;
- 用弹簧动画(
type: "spring"
)模拟“抛掷”效果,mass
(质量)越小越轻,damping
越小晃动越久。
应用场景:电商商品详情页、外卖点餐页、虚拟商品购买。
案例9:加载状态旋转进度条(无限循环)
效果描述:页面加载时,一个圆形进度条持续旋转,提示用户“内容正在加载”,避免用户等待时焦虑。
代码实现:
<motion.div
animate={{ rotate: 360 }} // 无限旋转360度
transition={{
repeat: Infinity, // 无限重复
repeatType: "loop", // 循环模式(也可设为"reverse"来回旋转)
duration: 1.5, // 每圈1.5秒
ease: "linear" // 线性缓动(旋转速度均匀)
}}
style={{
width: 40,
height: 40,
border: "4px solid #f3f3f3",
borderTop: "4px solid #3498db",
borderRadius: "50%",
margin: "20px auto"
}}
/>
参数解析:
repeat: Infinity
:动画结束后无限重复;ease: "linear"
:避免旋转时“卡顿”,保持匀速;- 边框样式:通过
border-top
设置高亮色,其他边框灰色,形成“加载环”效果。
应用场景:数据加载、文件上传、页面跳转等待。
案例10:模态框弹出与关闭(淡入+缩放)
效果描述:点击“查看详情”按钮时,模态框从中心“缩放+淡入”弹出;点击关闭按钮时,反向“缩放+淡出”消失。
代码实现(配合AnimatePresence
):
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
function ProductModal() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>查看详情</button>
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.8 }} // 初始状态:透明+缩小
animate={{ opacity: 1, scale: 1 }} // 最终状态:不透明+正常
exit={{ opacity: 0, scale: 0.8 }} // 关闭时:透明+缩小
transition={{ duration: 0.2 }}
style={{
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: "white",
padding: 20,
borderRadius: 10,
boxShadow: "0 4px 15px rgba(0,0,0,0.2)",
maxWidth: "90%",
zIndex: 1000
}}
>
<h2>商品详情</h2>
<p>魔法积木套装,包含100块魔法积木...</p>
<button onClick={() => setIsOpen(false)}>关闭</button>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
用户体验细节:
boxShadow
:添加阴影提升“浮层”感;zIndex: 1000
:确保模态框在最上层;- 入场/离场动画对称(
initial
和exit
相同),视觉更统一。
应用场景:详情展示、表单填写、确认对话框。
核心算法原理:Framer Motion的动画引擎
Framer Motion底层基于Web Animations API,通过以下步骤实现流畅动画:
- 状态计算:根据
initial
和animate
的值,计算属性(如x
、opacity
、scale
)的起始值和目标值; - 插值运算:使用
transition
定义的缓动函数(如线性、easeInOut、弹簧),在起始值和目标值之间生成连续的中间值(插值); - GPU加速:将动画属性(如
transform
、opacity
)标记为可GPU加速的属性,避免重排重绘,提升性能; - 帧渲染:以60fps(每秒60帧)的速度渲染动画帧,确保视觉流畅。
数学模型示例(弹簧动画):
弹簧动画的位移随时间变化的公式为:
x
(
t
)
=
e
−
d
⋅
t
⋅
A
⋅
cos
(
ω
⋅
t
+
ϕ
)
x(t) = e^{-d \cdot t} \cdot A \cdot \cos(\omega \cdot t + \phi)
x(t)=e−d⋅t⋅A⋅cos(ω⋅t+ϕ)
其中:
- ( d ):阻尼系数(
damping
),控制振动衰减速度; - ( A ):初始振幅(
stiffness
影响振幅); - ( \omega ):角频率(由
stiffness
和mass
计算); - ( \phi ):初始相位(通常为0)。
简单来说,弹簧动画会先“超过”目标值,再因阻尼逐渐稳定到目标位置,模拟真实弹簧的物理特性。
项目实战:用Framer Motion重构官网首屏动画
开发环境搭建
- 创建React项目:
npx create-react-app framer-demo
; - 安装Framer Motion:
npm install framer-motion
; - 启动开发服务器:
npm start
。
源代码实现(首屏标题+卡片入场动画)
// src/App.js
import { motion, AnimatePresence } from "framer-motion";
import "./App.css";
function App() {
return (
<div className="App">
{/* 首屏标题动画 */}
<motion.h1
initial={{ y: 50, opacity: 0 }} // 初始位置:下方50px+透明
animate={{ y: 0, opacity: 1 }} // 最终位置:归位+不透明
transition={{ duration: 0.6, ease: "easeOut" }}
className="hero-title"
>
魔法积木屋——会跳舞的网页
</motion.h1>
{/* 卡片列表动画 */}
<div className="card-container">
{[1, 2, 3].map((id) => (
<motion.div
key={id}
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{
duration: 0.6,
ease: "easeOut",
delay: id * 0.1 // 每个卡片延迟0.1秒(依次入场)
}}
className="card"
>
<h3>魔法卡片 {id}</h3>
<p>点击探索更多魔法效果</p>
</motion.div>
))}
</div>
</div>
);
}
export default App;
代码解读与分析
- 标题动画:通过
y
(垂直位移)和opacity
(透明度)的组合,实现“从下往上渐显”的效果,ease: "easeOut"
让动画结尾更柔和; - 卡片列表动画:每个卡片添加
delay: id * 0.1
,使它们按顺序入场(1号0秒延迟,2号0.1秒,3号0.2秒),形成“波浪式”动画,比同时入场更有层次感; - 性能优化:所有动画属性(
y
、opacity
)都是GPU加速属性,不会触发页面重排,保证流畅性。
实际应用场景总结
动画类型 | 典型页面位置 | 核心目标 |
---|---|---|
入场动画 | 首屏标题、卡片列表 | 引导用户注意力,减少“空白感” |
悬停/点击动画 | 按钮、图标、卡片 | 增强交互反馈,提升操作确认感 |
滚动触发动画 | 长页面分段内容(About Us) | 随滚动节奏展示内容,避免信息过载 |
展开/收起动画 | 导航菜单、折叠面板 | 节省空间,保持界面简洁 |
离场动画 | 模态框关闭、商品移除 | 明确“元素消失”的过程,避免突兀 |
工具和资源推荐
- 官方文档:Framer Motion Docs(包含完整API和示例);
- CodeSandbox模板:Framer Motion Starter(直接在线调试案例);
- 缓动函数工具:Easing Functions Cheat Sheet(查看各种缓动效果);
- 社区资源:Framer Motion GitHub(提交问题/查看源码);
- 设计参考:Awwwards(看优秀网站的动画设计)。
未来发展趋势与挑战
趋势1:与3D动画深度融合
随着WebGL和Three.js的普及,Framer Motion未来可能支持更复杂的3D变换(如绕X/Y/Z轴旋转、3D位移),结合3D模型实现更沉浸的交互。
趋势2:基于用户行为的智能动画
通过机器学习分析用户操作习惯(如滚动速度、点击位置),动态调整动画参数(如延迟时间、缓动类型),实现“个性化动画”。
挑战1:性能优化
虽然Framer Motion已做了GPU加速优化,但复杂动画(如成百上千个元素同时动)仍可能导致卡顿,需要开发者合理控制动画数量和复杂度。
挑战2:跨浏览器兼容
部分新特性(如useScroll
钩子)依赖较新的浏览器API,需做好降级处理(如旧浏览器使用静态布局)。
总结:学到了什么?
核心概念回顾
- motion组件:会动的HTML标签(如motion.div);
- initial/animate:动画的起点和终点;
- transition:控制动画节奏(时长、缓动、弹簧);
- whileHover/whileTap:交互触发动画的开关;
- AnimatePresence:管理元素离场动画。
概念关系回顾
Framer Motion通过“触发条件(悬停/加载)→ 定义状态(initial/animate)→ 控制节奏(transition)→ GPU渲染”的流程,将简单的属性设置转化为流畅的动画效果。就像给每个网页元素发了一张“动画通行证”,让它们能根据你的指令“跳舞”。
思考题:动动小脑筋
- 如何让案例1的入场动画“更有层次”?比如标题先动,卡片列表后动(提示:使用
delay
属性)。 - 案例2的悬停按钮,如何让它在“鼠标悬停时”先放大,“悬停一段时间后”再缩小(提示:使用
whileHover
的嵌套状态)? - 案例10的模态框,如何添加“背景模糊”的过渡效果(提示:给背景div添加
backdropFilter
动画)?
附录:常见问题与解答
Q:Framer Motion和CSS动画有什么区别?
A:CSS动画需要手动写关键帧(@keyframes),复杂动画(如弹簧、拖拽)实现困难;Framer Motion提供了更简洁的API(如type: "spring"
),且自动处理GPU加速,性能更优。
Q:动画卡顿怎么办?
A:检查是否动画了非GPU加速属性(如width
、margin
),尽量使用transform
和opacity
;减少同时动画的元素数量;使用motion
组件的layout
属性(自动处理布局变化动画)。
Q:如何在Vue/Angular中使用Framer Motion?
A:Framer Motion官方主要支持React,但社区有Vue适配版(如@vueuse/motion
),Angular可通过自定义指令集成。
扩展阅读 & 参考资料
- 《Framer Motion官方文档》:https://www.framer.com/motion/
- 《Web动画权威指南》(书籍):深入理解Web Animations API原理;
- 《CSS-Tricks动画教程》:https://css-tricks.com/almanac/properties/a/animation/
- 《Framer Motion最佳实践》:https://www.smashingmagazine.com/2021/05/animations-framer-motion/