Communitygithub.com

motion-patterns

適用於 React / Next.js 的生產級動畫模式——按鈕、模態框、通知、序列、頁面過渡、退出動畫、滾動和佈局——皆以 motion-foundations 令牌和彈性為基礎。當你需要在 React 或 Next.js 中為特定 UI 元素(按鈕、模態框、通知、序列、頁面過渡或滾動)添加動畫時使用。

motion-patterns 是什麼?

Motion Patterns 技能提供了一套可直接複製貼上的動畫模式,專門針對 React 和 Next.js 應用程式中最常見的 UI 動畫需求。每個模式都建立在 `motion-foundations` 的令牌和彈性之上,強制不使用內聯數值,以確保一致性和可維護性。它支援 AnimatePresence 條件渲染、序列列表、頁面過渡(特別是 Next.js App Router)、滾動觸發動畫以及佈局動畫。該技能也涵蓋了無障礙設計(如焦點陷阱和 ARIA 屬性)和效能考量,例如限制 `layout` 的使用範圍以及設定序列間隔的限制。它與 `motion-foundations`(提供底層令牌)和 `motion-advanced`(涵蓋拖曳和手勢等進階互動)相輔相成。

相容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/motion-patterns

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Motion Patterns

為最常見的 UI 動畫需求提供可複製貼上的模式。 此處的每個模式都建立在 motion-foundations 的令牌和彈性之上。 請勿在此定義新的時長或緩動值——請匯入它們。

啟用時機

  • 為按鈕、卡片、模態框或通知添加動畫
  • 使用序列效果建立列表進場
  • 在 Next.js App Router 中設定頁面過渡
  • 為條件式內容添加入場或出場動畫
  • 實作滾動揭示、滾動進度條或黏性章節
  • 建立展開卡片、手風琴或共享元素過渡

產出

此技能產出:

  • 無障礙、SSR 安全的標準 UI 元件動畫
  • 以 AnimatePresence 包裹的條件式渲染,具有正確的退出行為
  • 給 Next.js App Router 使用的頁面過渡包裝元件
  • 使用 useScroll + useTransform 的滾動揭示和滾動連結模式
  • 用於展開和淡入淡出元素的佈局動畫模式(layout、layoutId)

原則

  • 每個模式都從 motion-foundations 匯入。沒有原始數字。
  • 每個條件式渲染都包裹在 AnimatePresence 中,並帶有 key。
  • 退出動畫總是與進場動畫一起定義——絕不能事後才補上。
  • layout 僅用於小型、孤立的尺寸變化。大型子樹使用明確的轉換。

規則

  1. 始終在使用 AnimatePresence 包裹的條件式渲染中使用 key,且 key 必須在直接子元素上。沒有 key,退出動畫永遠不會觸發。
  2. 在定義 initial + animate 時,始終定義 exit。 沒有退出動畫的動畫是不完整的。
  3. 在頁面過渡中使用 mode="wait"。 進場必須等到出場完成後才能開始。
  4. 永遠不要在超過約 5 個子元素或深層巢狀 DOM 的子樹上使用 layout。 改用明確的 x / y 轉換。
  5. 序列間隔必須保持在 0.05s 到 0.10s 之間。 低於此範圍感覺機械,高於則感覺遲緩。
  6. 模態框必須始終包含: 焦點陷阱、Escape 鍵關閉、滾動鎖定、role="dialog"、aria-modal="true"。
  7. 滾動揭示使用 viewport={{ once: true }}。 在滾出時重複執行會分散注意力,沒有資訊價值。
  8. 所有令牌值都從 motion-foundations 匯入。 沒有內聯數字。

決策指南

選擇正確的模式

情況模式
元素出現 / 消失AnimatePresence
項目列表依序載入序列變體
在路由之間導航頁面過渡包裝器
元素在原地改變大小layout prop
相同元素在不同頁面上下文中移動layoutId
元素在滾入視野時進入whileInView
數值與滾動位置綁定useScroll + useTransform

何時使用 mode="wait" vs mode="sync"

模式使用時機
wait頁面過渡、內容切換(一次一個)
sync堆疊的通知、列表項目(重疊也可以)
popLayout從重新排列的列表中移除項目

核心概念

AnimatePresence 合約

三件事必須始終成立:

  1. AnimatePresence 包裹條件式
  2. 直接子元素具有 key
  3. 子元素具有 exit prop

任何一項缺失,退出動畫都會在無聲中失敗。

layout 與 layoutId

  • layout — 在原地動畫化元素自身的尺寸/位置變化
  • layoutId — 連結兩個不同的元素,在渲染之間將它們淡入淡出

在展開容器內的文字上使用 layout="position",以防止文字重新排列被動畫化。

程式碼範例

按鈕回饋

"use client"
import { motion } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

<motion.button
  whileHover={{ scale: motionTokens.scale.pop }}
  whileTap={{ scale: motionTokens.scale.press }}
  transition={springs.snappy}
/>

序列列表

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

const container = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.08,   // 在 0.05–0.10 規則範圍內
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden:  { opacity: 0, y: motionTokens.distance.md },
  visible: { opacity: 1, y: 0, transition: springs.gentle },
}

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.map((i) => (
    <motion.li key={i.id} variants={item} />
  ))}
</motion.ul>

模態框

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

// 在呼叫端包裹:
// <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence>

export function Modal({ onClose }: { onClose: () => void }) {
  return (
    <>
      {/* 覆蓋層 */}
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onClick={onClose}
      />

      {/* 面板 — 無障礙要求: 焦點陷阱、Escape 鍵關閉、
          滾動鎖定、role="dialog"、aria-modal="true" */}
      <motion.div
        role="dialog"
        aria-modal="true"
        className="fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6"
        initial={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        transition={springs.gentle}
      />
    </>
  )
}

通知堆疊

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<AnimatePresence mode="sync">
  {toasts.map((t) => (
    <motion.div
      key={t.id}
      layout
      initial={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      animate={{ opacity: 1, x: 0, scale: 1 }}
      exit={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      transition={springs.snappy}
    />
  ))}
</AnimatePresence>

頁面過渡 (Next.js App Router)

// components/page-transition.tsx
"use client"
import { motion, AnimatePresence } from "motion/react"
import { usePathname } from "next/navigation"
import { motionTokens } from "@/lib/motion-tokens"

const variants = {
  initial: { opacity: 0, y: motionTokens.distance.sm },
  enter:   { opacity: 1, y: 0 },
  exit:    { opacity: 0, y: -motionTokens.distance.sm },
}

export function PageTransition({ children }: { children: React.ReactNode }) {
  const pathname = usePathname()
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        variants={variants}
        initial="initial"
        animate="enter"
        exit="exit"
        transition={{
          duration: motionTokens.duration.normal,
          ease: motionTokens.easing.smooth,
        }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

滾動揭示

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<motion.div
  initial={{ opacity: 0, y: motionTokens.distance.lg }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-80px" }}   // once: true — 規則 7
  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
/>

滾動進度條

"use client"
import { motion, useScroll } from "motion/react"

export function ScrollProgress() {
  const { scrollYProgress } = useScroll()
  return (
    <motion.div
      className="fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full"
      style={{ scaleX: scrollYProgress }}
    />
  )
}

展開卡片

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

export function ExpandingCard({ title, body }: { title: string; body: string }) {
  const [expanded, setExpanded] = useState(false)

  return (
    <motion.div layout onClick={() => setExpanded(!expanded)} className="cursor-pointer">
      {/* layout="position" 防止文字重新排列被動畫化 */}
      <motion.h2 layout="position" className="font-semibold">
        {title}
      </motion.h2>

      <AnimatePresence>
        {expanded && (
          <motion.p
            key="body"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: motionTokens.duration.fast }}
          >
            {body}
          </motion.p>
        )}
      </AnimatePresence>
    </motion.div>
  )
}

共享元素淡入淡出

// 來源上下文
<motion.img layoutId="hero-image" src={src} className="w-16 h-16 rounded" />

// 目標上下文(相同的 layoutId — motion 處理過渡)
<motion.img layoutId="hero-image" src={src} className="w-full rounded-xl" />

手風琴

<motion.div
  initial={false}
  animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0 }}
  style={{ transformOrigin: "top", overflow: "hidden" }}
  transition={{
    duration: motionTokens.duration.normal,
    ease: motionTokens.easing.smooth,
  }}
> {children}
</motion.div>

端對端範例

一個在掛載時進場、處理條件式存在,並尊重減少動畫偏好的序列列表——結合了令牌、彈性、AnimatePresence 以及來自 motion-foundations 的無障礙鉤子:

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"

const containerVariants = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08, delayChildren: 0.1 },
  },
}

function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
  const safe = useSafeMotion(motionTokens.distance.sm)
  return (
    <motion.li
      variants={{
        hidden:  safe.initial,
        visible: safe.animate,
      }}
      exit={safe.exit}
      transition={springs.gentle}
      className="flex items-center justify-between p-3 rounded-lg bg-white shadow-sm"
    >
      <span>{label}</span>
      <button onClick={onRemove}>Remove</button>
    </motion.li>
  )
}

export function AnimatedList({ items, onRemove }: {
  items: { id: string; label: string }[]
  onRemove: (id: string) => void
}) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="space-y-2"
    >
      <AnimatePresence mode="popLayout">
        {items.map((item) => (
          <ListItem
            key={item.id}
            label={item.label}
            onRemove={() => onRemove(item.id)}
          />
        ))}
      </AnimatePresence>
    </motion.ul>
  )
}

限制 / 非目標

此技能不涵蓋:

  • 令牌和彈性定義 → 請參閱 motion-foundations
  • 拖曳互動、滑動手勢、可重新排序列表 → 請參閱 motion-advanced
  • 文字動畫(字詞/字元揭示、計數器) → 請參閱 motion-advanced
  • SVG 路徑繪製或變形 → 請參閱 motion-advanced
  • 自訂動畫鉤子 → 請參閱 motion-advanced
  • 不使用 motion/react 的純 CSS 過渡

反模式

反模式違反規則修正
AnimatePresence 子元素缺少 key規則 1為直接子元素添加穩定的 key
initial + animate 沒有 exit規則 2始終一起定義這三者
頁面過渡沒有 mode="wait"規則 3在 AnimatePresence 上添加 mode="wait"
在包含 50 個項目的列表上使用 layout規則 4使用 mode="popLayout" 或明確的轉換
在 10 個項目的列表上使用 staggerChildren: 0.2規則 5限制在 0.08–0.10
沒有焦點陷阱的模態框規則 6添加 focus-trap-react 或 Radix Dialog
whileInView 沒有 viewport={{ once: true }}規則 7重複進場會分散注意力,沒有資訊價值
內聯 transition={{ duration: 0.3 }}規則 8使用 motionTokens.duration.normal

相關技能

  • motion-foundations — 定義了所有令牌、彈性、useSafeMotion 鉤子以及 SSR 防護,此處的每個模式都匯入了這些內容。必須先設定好。
  • motion-advanced — 將這些模式擴展到拖曳、手勢、SVG、文字、自訂鉤子和命令式序列。不會重新定義此技能的任何模式。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相關技能