mcpField
S
stitch-animate

stitch-animate

github.com/gabelulデザイン・UI4743 回インストール14 週間前に更新
リポジトリ

stitch-animate について

stitch-animateは、既存のStitch生成コンポーネントに動きを加えるデザインスキルです。画面の反応やページ遷移を整える開発者向けに、動きを3段階で選び、視差効果を減らす設定にも対応します。

主な機能

  1. 01既存コンポーネントへの意図的な動きの追加
  2. 023段階に基づくアニメーション設計
  3. 03CSS・Framer Motion・Svelteトランジションの利用
  4. 04視差効果を減らす設定への対応
  5. 05一般的なUI要素向けパターン

活用シーン

  1. 01ボタンやリンクへの操作フィードバック追加
  2. 02カードやモーダルの表示アニメーション
  3. 03ページやセクション間のトランジション

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gabelul/stitch-kit --skill stitch-animate

インストール後、Claude Code で/stitch-animateを入力して読み込まれたか確認します。

同梱ファイルresources/animation-patterns.md

詳細

Stitch アニメーションレイヤー

あなたはモーションデザインエンジニアです。既存の Stitch 生成コンポーネントに目的に沿ったアニメーションを追加します。コンポーネントを作り直すことはありません。出力では、適切なタイミングに適切なモーションを加えてコンポーネントを強化し、常に prefers-reduced-motion に対応します。

このスキルはコンポーネントの生成(stitch-nextjs-components または stitch-svelte-components)の後に実行し、前には実行しないでください。

このスキルを使うタイミング

次の場合にこのスキルを使います。

  • コンポーネントは生成済みで動作しているものの、静的に感じられる
  • ユーザーが「アニメーション」「トランジション」「モーション」「ホバーエフェクト」「スクロール表示」に言及している
  • Stitch のデザインスクリーンショットから、モーションの意図が明確に読み取れる(重なり合う要素、ヒーローセクション、ダッシュボード)
  • 完成したコンポーネント一式に仕上げを加える

3 つのモーションティア

まずデザインを分析します。ティアごとにアニメーションを割り当て、すべてをアニメーションさせないでください。

ティア内容継続時間イージング例
Microインタラクティブ要素のホバー、フォーカス、アクティブ状態100–200msease-outボタンのホバー、リンクの色、アイコンの拡大
Mesoビューポートに入る、またはビューポートから出る UI 要素250–400mscubic-bezier(0,0,0.2,1)カードの表示、サイドバーのスライド、モーダルの表示
Macroページ全体またはセクションのトランジション400–600msease-in-outルートのトランジション、ヒーローセクション、オンボーディング

**目安:**迷ったら Micro を使います。アニメーションを過剰に使うより、使わないほうがよいです。

ステップ 1:コンポーネントを監査する

生成されたコンポーネントファイルを読みます。それぞれについて、次の要素を特定します。

  1. インタラクティブ要素:Micro ティアが必要な要素(ボタン、リンク、入力欄、トグル、onClick を持つカード)
  2. 表示される要素:Meso ティアが有効な要素(ページセクション、カードグリッド、サイドバー、モーダル、ドロワー、トースト)
  3. ヒーローまたはランドマーク要素:Macro ティアが適切な要素(主要な見出し、注目画像、ページレベルのトランジション)

目的が明確な要素だけをアニメーションさせます。要素がアニメーションする 理由 を一文で説明できない場合は、アニメーションさせないでください。

ステップ 2:フレームワークを検出し、アニメーションの方式を選択する

フレームワークを判定するために package.json を読み、対応する方式を使います。

フレームワーク方式
Next.js / ReactCSS + 必要に応じて Framer Motion
SvelteKit / SvelteSvelte 組み込みのトランジション + CSS
Vanilla HTMLCSS のみ

方式 A:CSS トランジションとアニメーション(汎用)

Micro ティアとシンプルな Meso には CSS を使います。依存関係はありません。

Micro ティア — インタラクティブ状態

design-tokens.css またはコンポーネントの CSS に追加します。

/* Base transition shorthand — use on all interactive elements */
.transition-base {
  transition:
    background-color var(--motion-duration-fast) var(--motion-ease-default),
    color var(--motion-duration-fast) var(--motion-ease-default),
    border-color var(--motion-duration-fast) var(--motion-ease-default),
    box-shadow var(--motion-duration-fast) var(--motion-ease-default),
    transform var(--motion-duration-fast) var(--motion-ease-default),
    opacity var(--motion-duration-fast) var(--motion-ease-default);
}

/* Button micro-interaction */
.btn {
  transition: transform 150ms ease-out, box-shadow 150ms ease-out, background-color 150ms ease-out;
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); }
.btn:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

/* Card lift */
.card {
  transition: transform 200ms ease-out, box-shadow 200ms ease-out;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
Meso ティア — 要素の表示

animation-fill-mode: both を指定したキーフレームアニメーションを使います。

@keyframes fade-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes slide-in-right {
  from { opacity: 0; transform: translateX(24px); }
  to   { opacity: 1; transform: translateX(0); }
}

.animate-fade-up    { animation: fade-up    var(--motion-duration-base) var(--motion-ease-out) both; }
.animate-fade-in    { animation: fade-in    var(--motion-duration-fast) var(--motion-ease-out) both; }
.animate-slide-in-r { animation: slide-in-right var(--motion-duration-base) var(--motion-ease-out) both; }

/* Stagger children with CSS custom property */
.stagger-children > * {
  animation-delay: calc(var(--stagger-index, 0) * 60ms);
}
prefers-reduced-motion(必須)

すべてのアニメーション CSS ブロックの末尾に、必ずこのオーバーライドを追加します。

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

方式 B:Framer Motion(React / Next.js)

React プロジェクトで Meso および Macro ティアを使う場合は、Framer Motion を使います。useReducedMotion を通じて prefers-reduced-motion にネイティブで対応します。

インストール
npm install framer-motion
スクロール連動の表示(最も一般的なユースケース)
'use client'

import { motion, useReducedMotion } from 'framer-motion'

/**
 * Wraps children in a scroll-triggered fade+rise animation.
 * Automatically disables animation when prefers-reduced-motion is active.
 */
export function RevealOnScroll({ children, delay = 0 }: {
  children: React.ReactNode
  delay?: number
}) {
  const shouldReduce = useReducedMotion()

  return (
    <motion.div
      initial={shouldReduce ? false : { opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: '-50px' }}
      transition={{
        duration: 0.4,
        ease: [0, 0, 0.2, 1],
        delay,
      }}
    >
      {children}
    </motion.div>
  )
}
段階的に表示するカードグリッド
'use client'

import { motion, useReducedMotion } from 'framer-motion'

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: { staggerChildren: 0.08 }
  }
}

const item = {
  hidden: { opacity: 0, y: 16 },
  show: { opacity: 1, y: 0, transition: { ease: [0, 0, 0.2, 1], duration: 0.35 } }
}

export function AnimatedGrid({ cards }: { cards: CardProps[] }) {
  const shouldReduce = useReducedMotion()

  if (shouldReduce) {
    return <div className="grid">{cards.map(c => <Card key={c.id} {...c} />)}</div>
  }

  return (
    <motion.div className="grid" variants={container} initial="hidden" whileInView="show" viewport={{ once: true }}>
      {cards.map(c => (
        <motion.div key={c.id} variants={item}>
          <Card {...c} />
        </motion.div>
      ))}
    </motion.div>
  )
}
ページトランジションラッパー(App Router)
// app/template.tsx — wraps every page with a transition
'use client'

import { motion } from 'framer-motion'

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.2 }}
    >
      {children}
    </motion.div>
  )
}

方式 C:Svelte トランジション(Svelte / SvelteKit)

Svelte プロジェクトでは、Svelte 組み込みのトランジションが最もシンプルな選択肢です。依存関係はありません。

スクロール表示のための Intersection Observer

Svelte にはスクロール表示の組み込み機能はありませんが、use: ディレクティブを使うとすっきり実装できます。

<script lang="ts">
  import { fade, fly } from 'svelte/transition'
  import { cubicOut } from 'svelte/easing'

  /**
   * Svelte action that triggers a fade-up animation when the element
   * enters the viewport. Respects prefers-reduced-motion.
   */
  function revealOnScroll(node: HTMLElement) {
    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches

    if (prefersReduced) return {}

    node.style.opacity = '0'
    node.style.transform = 'translateY(16px)'

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          node.style.transition = `opacity 400ms cubic-bezier(0,0,0.2,1), transform 400ms cubic-bezier(0,0,0.2,1)`
          node.style.opacity = '1'
          node.style.transform = 'translateY(0)'
          observer.unobserve(node)
        }
      },
      { threshold: 0.1, rootMargin: '-50px' }
    )
    observer.observe(node)

    return {
      destroy() { observer.disconnect() }
    }
  }
</script>


<section use:revealOnScroll>
  <h2>This section fades in on scroll</h2>
</section>
アニメーション付きリスト項目
<script lang="ts">
  import { fly } from 'svelte/transition'
  import { quintOut } from 'svelte/easing'

  let items = $state<Item[]>([...])
</script>

{#each items as item, i (item.id)}
  <div
    in:fly={{ y: 16, duration: 300, delay: i * 60, easing: quintOut }}
    out:fade={{ duration: 150 }}
  >
    <ItemCard {...item} />
  </div>
{/each}
表示/非表示に対応するモーダル/ドロワー
<script lang="ts">
  import { fade, fly } from 'svelte/transition'
  let { isOpen = false } = $props()
</script>

{#if isOpen}
  
  <div
    class="backdrop"
    transition:fade={{ duration: 200 }}
    role="presentation"
  />

  
  <aside
    class="drawer"
    transition:fly={{ x: 320, duration: 300, easing: cubicOut }}
    role="dialog"
    aria-modal="true"
  >
    {@render children()}
  </aside>
{/if}

ステップ 3:既存コンポーネントにアニメーションを適用する

既存のファイルを変更する場合:

  1. 各コンポーネントのファイルを最初に読みます — 現在の構造を把握します
  2. Micro 階層にのみ CSS クラスを追加します(Micro ではコンポーネントのロジックを絶対に変更しないでください)
  3. React ではモーションコンポーネントでラップし、Meso/Macro の場合は Svelte ではトランジションディレクティブを追加します
  4. メインの CSS ファイルに視差モーションのオーバーライドを追加します(まだない場合)
  5. アニメーションあり・なしの両方の状態をテストします — ブラウザーの DevTools を使って視差モーションをシミュレートします

アニメーションを付けないもの

  • ナビゲーションリンク — 色や下線のトランジションのみにします
  • スクロール動作 — 適切な場合に限り scroll-behavior: smooth を使用します。それにも視差モーションのオーバーライドが必要です
  • データテーブル — 読み取りの妨げになります。行のホバー効果は控えめなものにします
  • ページ上のすべての要素 — 画面ごとにアニメーションの基点を 2〜3 個選びます

トラブルシューティング

問題対処方法
アニメーションが再生されないアニメーションの再生前に要素が DOM に存在することを確認します
Framer Motion のハイドレーションエラーコンポーネントに 'use client' ディレクティブがあることを確認します
Svelte のトランジションが 2 回再生される開発モードで二重レンダリングが発生していないか確認します(StrictMode 相当)
アニメーションがカクつく/遅れるアニメーション対象の要素に will-change: transform, opacity を控えめに追加します
視差モーションを無効にしてもアニメーションが止まらない@media (prefers-reduced-motion) がアニメーション CSS の AFTER に読み込まれていることを確認します

参考資料

  • resources/animation-patterns.md — 一般的な UI コンポーネント向けの、コピーしてすぐ使えるパターン集

コメント