首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

前端框架

Framer Motion 打造比最棒還要棒的動畫

By Kurau·2023-05-16·Updated 2026-05-09·5 分鐘閱讀

Framer Motion — React 動畫首選

TL;DR
declarative API,把動畫寫成 props(initial / animate / exit / whileHover)。內建手勢(drag、tap、hover)、layout 動畫、AnimatePresence(mount/unmount)。2024 改名 Motion,但 React 套件 import 仍叫 framer-motion。2026 年 React 動畫的首選庫。

下面這顆球就是 framer-motion 的內建 drag 手勢 + spring transition(拖了放手會彈回):


安裝

npm install framer-motion react-intersection-observer
bash
套件用途
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>
  );
}
tsx

motion.div 是 enhanced div,接受所有原生 div props 加上動畫 props。motion.span、motion.button、motion.svg 等都有對應版本。


常用 props

Prop說明
initial動畫起始狀態(mount 那一刻)
animate動畫目標狀態(mount 後過渡到這)
exit離開動畫(需配合 <AnimatePresence>)
transitionduration / ease / spring 設定
whileHoverhover 時的狀態
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>
tsx

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>
  );
}
tsx

適合需要更細控制的場景(例:用 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>
tsx

父元素只用語意化的 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>
  );
}
tsx

沒包 <AnimatePresence> 的話,React unmount 直接消失,沒離開動畫。這個 wrapper 會 延後 unmount 到 exit 動畫播完。


Layout 動畫(magic move)

<motion.div layout>
  {/* 內容變化時,框會平滑補間到新尺寸/位置 */}
</motion.div>
tsx

超強功能:當元素的尺寸 / 位置因為 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>
tsx

頁面切換時兩個 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>
tsx

比 HTML5 DragEvent 強大很多,支援手機 touch、慣性、彈性,且離散 vs 連續 drag 都好寫。


Spring 物理動畫

<motion.div
  animate={{ x: 100 }}
  transition={{ type: 'spring', stiffness: 100, damping: 10 }}
/>
tsx
參數預設說明
stiffness100彈簧硬度,高 = 衝得快
damping10阻尼,高 = 快速停下不彈
mass1質量,高 = 慣性大

跟 React-Spring 同源,參數意義一致,可以互換經驗。


Framer Motion vs React-Spring

比較項Framer MotionReact-Spring
API 風格declarative propshook-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 年新特性

改名 Motion
2024 起 Framer Motion 改名為 Motion,分成 motion/react(原 framer-motion)+ motion/vue(Vue 版)+ vanilla JS 版。
npm install motion
bash
import { motion } from 'motion/react';   // 新 import
// import { motion } from 'framer-motion';  // 舊 import 仍 work
tsx

新版本同時支援 Server Components(部分 API 可在 RSC 裡用)。

目錄

    ◆ 相關文章

    • React-Spring 打造最棒的動畫啊~~

      2026-05-09
    • HTML React 打字機效果

      2026-05-09
    • Redux

      2026-05-09
    • react-app-env.d.ts

      2026-05-09
    ← 上一篇npm --save 到底是什麼 --save-dev 不一樣嗎下一篇 →SCSS 編譯 前綴問題

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章