Framer Motion 打造比最棒還要棒的動畫
Framer Motion — React 動畫首選
TL;DRdeclarative API,把動畫寫成 props(initial / animate / exit / whileHover)。內建手勢(drag、tap、hover)、layout 動畫、AnimatePresence(mount/unmount)。2024 改名 Motion,但 React 套件 import 仍叫 framer-motion。2026 年 React 動畫的首選庫。
下面這顆球就是 framer-motion 的內建
drag手勢 + springtransition(拖了放手會彈回):
安裝
npm install framer-motion react-intersection-observer
| 套件 | 用途 |
|---|---|
framer-motion | 動畫核心 |
react-intersection-observer | 捲動觸發必備(偵測元素是否進入 viewport) |
基本用法
import { motion } from 'framer-motion';
function FadeIn() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Hello!
</motion.div>
);
}
motion.div 是 enhanced div,接受所有原生 div props 加上動畫 props。motion.span、motion.button、motion.svg 等都有對應版本。
常用 props
| Prop | 說明 |
|---|---|
initial | 動畫起始狀態(mount 那一刻) |
animate | 動畫目標狀態(mount 後過渡到這) |
exit | 離開動畫(需配合 <AnimatePresence>) |
transition | duration / ease / spring 設定 |
whileHover | hover 時的狀態 |
whileTap | 按下時的狀態 |
whileInView | 進入 viewport 時的狀態(內建,不用 react-intersection-observer) |
variants | 預定義狀態集合,讓父子動畫協調 |
layout | 布局變化自動補間(magic move) |
捲動觸發(兩種寫法)
寫法 1:內建 whileInView(推薦)
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }} // once:只觸發一次;amount:30% 進入才算
transition={{ duration: 0.6 }}
>
Scroll-triggered content
</motion.div>
Framer Motion 內建,不用裝額外套件。viewport.amount: 0.3 等於 IntersectionObserver threshold: 0.3。
寫法 2:react-intersection-observer
import { motion } from 'framer-motion';
import { useInView } from 'react-intersection-observer';
function ScrollReveal({ children }) {
const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1 });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
>
{children}
</motion.div>
);
}
適合需要更細控制的場景(例:用 inView 做別的事不只動畫)。一般情況用 whileInView 就好。
Variants(父子動畫協調)
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15, // ⭐ 子元素 stagger 0.15s
delayChildren: 0.3, // 第一個子元素延遲 0.3s 才開始
},
},
};
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 },
};
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item.id} variants={itemVariants}>
{item.text}
</motion.li>
))}
</motion.ul>
父元素只用語意化的 state 名(hidden/visible),子元素自己定義細節。整個 tree 一起切換,自動 stagger。
AnimatePresence(離開動畫)
import { AnimatePresence, motion } from 'framer-motion';
function Modal({ open, content }: Props) {
return (
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.2 }}
className="modal"
>
{content}
</motion.div>
)}
</AnimatePresence>
);
}
沒包 <AnimatePresence> 的話,React unmount 直接消失,沒離開動畫。這個 wrapper 會 延後 unmount 到 exit 動畫播完。
Layout 動畫(magic move)
<motion.div layout>
{/* 內容變化時,框會平滑補間到新尺寸/位置 */}
</motion.div>
超強功能:當元素的尺寸 / 位置因為 React 重新渲染而改變(例:列表排序、卡片展開),layout 自動把 CSS 變化變成動畫,不用寫任何 keyframe。
// 共享元素過渡(shared layout)
<motion.div layoutId="hero-image">
<img src="..." />
</motion.div>
// 從另一個畫面也用相同 layoutId
<motion.div layoutId="hero-image">
<img src="..." />
</motion.div>
頁面切換時兩個 layoutId 相同的元素會自動補間,做頁面共享元素效果(例:從列表縮圖過渡到詳細頁大圖)。
Drag
<motion.div
drag // 啟用
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 100 }}
dragElastic={0.2} // 拖出 constraint 時的彈性
dragMomentum // 放手後慣性滑行
whileDrag={{ scale: 1.05 }} // 拖曳時放大
onDragEnd={(e, info) => {
if (info.offset.x > 100) handleSwipeRight();
}}
>
Drag me
</motion.div>
比 HTML5 DragEvent 強大很多,支援手機 touch、慣性、彈性,且離散 vs 連續 drag 都好寫。
Spring 物理動畫
<motion.div
animate={{ x: 100 }}
transition={{ type: 'spring', stiffness: 100, damping: 10 }}
/>
| 參數 | 預設 | 說明 |
|---|---|---|
stiffness | 100 | 彈簧硬度,高 = 衝得快 |
damping | 10 | 阻尼,高 = 快速停下不彈 |
mass | 1 | 質量,高 = 慣性大 |
跟 React-Spring 同源,參數意義一致,可以互換經驗。
Framer Motion vs React-Spring
| 比較項 | Framer Motion | React-Spring |
|---|---|---|
| API 風格 | declarative props | hook-based |
| 手勢 | 內建 drag/tap/hover | 要 @use-gesture |
| Layout 動畫 | 有 magic move | 要自己寫 |
| AnimatePresence | 內建 | useTransition |
| 上手速度 | ⭐ 快 | 較慢 |
| 動畫質感 | 流暢可控 | 更物理感 |
| Bundle | ~50KB | ~25KB |
怎麼選
- 一般 React 專案 → Framer Motion 是預設選擇
- 3D / 物理感互動 → React-Spring(配 react-three-fiber)
- 微互動(button hover、icon transition) → CSS transitions 就夠
2026 年新特性
改名 Motion2024 起 Framer Motion 改名為 Motion,分成 motion/react(原 framer-motion)+ motion/vue(Vue 版)+ vanilla JS 版。npm install motionimport { motion } from 'motion/react'; // 新 import // import { motion } from 'framer-motion'; // 舊 import 仍 work
新版本同時支援 Server Components(部分 API 可在 RSC 裡用)。