前端動效
你負責設計、實現、審查、優化前端動效。預設假設專案是 React + TypeScript;純 JS 或其他框架按專案現狀,不把 React 方案強加給 Vue 或原生專案。 動效是增強,不是裝飾:先保證靜態版本好看、能用,再疊加動效。 未經用戶確認,不得新增任何依賴。
§0 動手前必做:依賴與設計系統探測
動手前按順序完成五步;任何一步缺失,先補探測再寫程式碼。
- 讀
package.json,並讀pnpm-lock.yaml/package-lock.json/yarn.lock裡實際鎖定的版本,確認已裝動畫庫及其大版本。 - 讀
tailwind.config.*、全域樣式(globals.css/index.css/app.css)、theme 檔案,找出現有 token、時長、緩動與命名。 - 有 motion 就不用 GSAP,有 GSAP 就不加 motion;同一需求不允許兩個庫並存。
- 不得擅自新增依賴。確需新增時,先說明包名、min+gzip 體積量級、替代方案、為什麼現有庫做不到,等用戶確認後再裝。
- 沿用專案已有的命名、檔案組織、程式碼風格與 token 名,不引入第二套動效體系。
| 探測到什麼 | 預設怎麼做 |
|---|---|
| 已有 motion | 互動、進出場、佈局動畫全部走 motion,不引入 GSAP |
| 已有 framer-motion 舊版 | 沿用現有包名與 API,不擅自遷移到 motion;升級單獨提、單獨做 |
| 已有 GSAP | 時間軸與滾動聯動走 GSAP + ScrollTrigger;簡單 hover 仍用 CSS transition,不加 motion |
| 只有 Tailwind | 用 transition / duration / ease 原子類配合全域 CSS 變數,不裝庫 |
| 純 CSS 專案 | 只用 CSS transition/animation + WAAPI,不裝 JS 動畫庫 |
| Next.js App Router | 動畫元件標 "use client";View Transitions 與 motion 按客戶端元件處理,不做跨路由共享狀態 hack |
| Vue | 用原生 CSS 或 Vue 生態的 motion 方案,不把 React 專用庫硬塞進去 |
§1 反模式清單(不可違反)
逐條對照;命中任意一條即退回重寫,不做「這次先這樣」的妥協。
| 禁止 | 替代 |
|---|---|
transition: all .3s | 顯式列出 transform, opacity,必要時加 color |
全站統一 ease-in-out | 按進場/出場/雙向三種語境選曲線(見 §3) |
元素從 opacity: 0 + scale(.8) 彈入 | opacity: .9→1 + translateY(8px)→0 |
無腦 linear | 僅進度條與無限循環裝飾可用 |
| 同一螢幕同時有 3 個以上元素在動 | 只保留 1 個視覺焦點,其餘靜止或極弱 |
hover 用 transform: translateY(-4px) 但父容器無預留空間 | 用 scale 或給父級 padding 預留 |
| 只做進場不做出場 | 必須成對設計,且出場快於進場 |
大面積 filter: blur() / drop-shadow 做動畫 | 改用預渲染模糊圖層,或降低動效範圍 |
| 動畫層覆蓋 CTA / 表單 | 動效層 pointer-events: none,或避開互動區 |
| 頁面首屏 loading 動畫超過 1.5s | 超過則先給骨架屏 |
| 自訂游標 + 拖尾(除非用戶明確要求) | 不做 |
| 給每個列表項都加逐字/逐幀複雜動畫 | 用 30–50ms stagger 的整體位移即可 |
用 setTimeout 串聯動畫 | 用 transition/animation 事件、onAnimationComplete、timeline |
逐幀 el.style.left = x | WAAPI / rAF / 庫的 timeline |
閾值理由:首屏等待超過 1.5s,用戶開始懷疑頁面卡死,所以先給骨架屏;同屏超過 3 個獨立運動時視覺焦點失效,用戶不知道該看哪裡。
§2 選型決策樹(精簡版)
先查表定方案,再進 §3 定數值,最後才寫程式碼。
| 場景 | 首選 | 不該用 |
|---|---|---|
| hover / press / toggle 反饋 | CSS transition | GSAP(過重) |
| 元件進出、列表增刪、佈局變化 | motion(AnimatePresence + layout) | 手寫 height 動畫 |
| 路由/頁面轉場 | View Transitions API,或 motion layoutId | 全螢幕遮罩 + 延時 |
| 複雜時間軸、序列編排、滾動聯動 | GSAP + ScrollTrigger | CSS keyframes 堆砌 |
| 3D / 粒子 / 物理 / shader | three.js + React Three Fiber + drei | 用 CSS 假裝 3D |
| 設計師給的向量動畫 | dotLottie / Lottie | 轉成 GIF |
| 簡單圖示或路徑動畫 | SVG + CSS | 引入 Lottie 執行時 |
| 資料圖表動效 | 庫自帶動畫(如 Recharts/visx 的 transition) | 自己 tween 路徑 |
| 背景裝飾(網格/波浪/光暈) | CSS 或 canvas + requestAnimationFrame,且可暫停 | three.js(除非真要 3D) |
| 數字/金額變化 | tabular-nums + 簡單 tween | 逐幀重排佈局 |
完整選型邏輯、提問順序與「CSS → 輕庫 → 重庫」升級階梯見 references/decision-tree.md。
§3 數值規範(不許憑感覺)
所有時長、曲線、幅度從下表取值;表裡沒有的值,先論證再新增。
時長
| 項 | 值 |
|---|---|
| hover / press | 120–180ms |
| toggle / 展開收起 | 200–300ms |
| 進場 | 240–400ms |
| 出場 | 進場時長 × 0.6–0.7 |
| 頁面轉場 | 300–500ms |
| 佈局重排(layout 動畫) | 300–500ms |
| stagger 間隔 | 30–50ms,整組總時長 ≤ 500ms |
| 超過 600ms | 必須能說明理由,否則砍掉 |
緩動
| 緩動 | 曲線 |
|---|---|
| 進場 | cubic-bezier(0.16, 1, 0.3, 1) |
| 出場 | cubic-bezier(0.4, 0, 1, 1) |
| 雙向/狀態切換 | cubic-bezier(0.4, 0, 0.2, 1) |
| 位移/拖拽/手勢 | spring: stiffness 300–500,damping 30–40 |
| linear | 僅進度條、無限循環裝飾 |
幅度
| 幅度 | 值 |
|---|---|
| 位移 | 微 4–8px / 中 12–24px / 大 32–64px / >80px 僅全螢幕轉場 |
| 透明度 | 微互動 .9→1;進場 .6→1;0→1 僅全螢幕遮罩與模態 |
| 縮放 | 僅 0.96–1.04 區間 |
Spring
| 參數 | 值 | 理由 |
|---|---|---|
| stiffness | 300–500 | 低於 300 遲鈍,高於 500 抖動像故障 |
| damping | 30–40 | 低於 30 回彈過頭,高於 40 沒有回彈感 |
| mass | 0.8–1 | 慣性可感知的最小改動 |
區間理由,一句話一條:
- hover / press 120–180ms:低於 120ms 讀不出狀態變化,高於 180ms 反饋遲滯,用戶以為點空了。
- 進場 240–400ms:低於 240ms 讀不出位移方向,高於 400ms 用戶已經開始等待。
- 出場 × 0.65:離開不需要被看清,慢就是拖沓。
- stagger 30–50ms:低於 30ms 像同時出現,高於 50ms 整組越過 500ms 的耐心線。
這些值已落成 CSS 變數與 TS 常數,見 references/tokens.md;所有程式碼引用 token,禁止硬編碼數字。
§4 效能與可訪問性紅線
- 只動畫
transform/opacity;要動畫尺寸用 FLIP 或庫的layout能力。 - 會持續動畫的層加
will-change: transform,動畫結束後移除。 - 長列表虛擬化;離螢幕元素用 IntersectionObserver 暫停。
prefers-reduced-motion: reduce是必做分支,不是可選加分項。降級後仍要有清晰的狀態變化:通常退化為 ≤120ms 的 opacity 交叉淡入(120ms 足夠讀出狀態切換,又不構成需要被理解的動畫),或直接切換。- 不使用 3 次/秒以上的閃爍(WCAG 2.3.1 的光敏性閾值就是每秒 3 次)。
- 動畫期間不奪取焦點、不打亂 tab 順序;鍵盤焦點環不參與動畫。
- 設計時按「靜態版本也好看」來構圖,動效只是增強。
WCAG 條款、aria-live、測試方法見 references/accessibility.md。
§5 實現鐵律
- 進出場成對設計,且出場更快。
- 一螢幕只留一個視覺焦點。
- stagger 用 30–50ms,整組不超過 500ms。
- 裝飾動效層必須
pointer-events: none。 - 用 transition/animation 事件、
onAnimationComplete或 timeline 串聯,禁止setTimeout。 - CSS 或 Framer 能做的不用 GSAP。
- 能用
transform就不動畫width/height。 - 動畫時長進設計評審,不靠「感覺慢一點」口頭調。
- 先減後加:能刪的動效先刪,刪不掉的再優化。
§6 審查模式
審查現有動效時,每條按固定四段輸出:
問題 / 依據(引用 §1–§4 的條款)/ 建議程式碼 / 影響(時長、效能、可訪問性)
規則:
- 只報真問題。不為湊數改寫本來合理的實現。
- 最多 8 條,按嚴重度排序:可訪問性 > 效能 > 時長/曲線 > 風格。
- 結尾寫一段「這些地方是對的」,列 1–3 條,避免用戶把合理實現誤改掉。
掃描命令(唯讀,不提供 --fix;任何自動改動畫都會破壞設計意圖):
node ~/.claude/skills/frontend-animation/scripts/audit-animation.mjs <專案目錄> --json
腳本只報告問題與位置,修復由人決定。
§7 需要 X 時讀什麼
| 症狀/需求 | 檔案 |
|---|---|
| 不知道用 CSS 還是庫、要在框架間選、要按順序提問 | references/decision-tree.md |
| 要落 token、接 Tailwind、寫 CSS 變數或 TS 常數 | references/tokens.md |
| 查 motion / GSAP / three.js+R3F / Lottie / View Transitions 的安裝、API、版本陷阱 | references/libraries.md |
| 要現成模板(列表進場 / 路由轉場 / 拖拽 / 滑鼠跟隨 / 數字滾動 / 3D 翻轉 / 循環背景 / 骨架屏 / 磁吸按鈕 / Lottie) | references/patterns.md |
| 選緩動曲線、要 spring 的 linear() 近似、解釋某條曲線為什麼 | references/easing.md |
| reduced-motion 降級、WCAG、閃爍與焦點、測試方法 | references/accessibility.md |
| 要一份帶完整 reduced-motion 分支的參考元件 | assets/template-component.tsx |
| 要掃描現有程式碼裡的動畫問題 | scripts/audit-animation.mjs |
§8 提交前自檢(8 問,逐條作答)
- 這個動效必要嗎?去掉後靜態版本仍然好看嗎?
- 是否只動 transform / opacity?
- 時長、曲線、幅度是否都在 §3 內?
- 進出場是否成對,出場是否更快?
- reduced-motion 是否有分支,降級後狀態是否仍可辨?
- 是否新增了依賴?未經確認即違規。
- 是否有 >3 次/秒閃爍、是否在動畫期間搶焦點?
- 一螢幕是否只有一個視覺焦點?
有一問答不上來,就停下修,不提交。