stitch-animate について
stitch-animateは、既存のStitch生成コンポーネントに動きを加えるデザインスキルです。画面の反応やページ遷移を整える開発者向けに、動きを3段階で選び、視差効果を減らす設定にも対応します。
主な機能
- 01既存コンポーネントへの意図的な動きの追加
- 023段階に基づくアニメーション設計
- 03CSS・Framer Motion・Svelteトランジションの利用
- 04視差効果を減らす設定への対応
- 05一般的なUI要素向けパターン
活用シーン
- 01ボタンやリンクへの操作フィードバック追加
- 02カードやモーダルの表示アニメーション
- 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–200ms | ease-out | ボタンのホバー、リンクの色、アイコンの拡大 |
| Meso | ビューポートに入る、またはビューポートから出る UI 要素 | 250–400ms | cubic-bezier(0,0,0.2,1) | カードの表示、サイドバーのスライド、モーダルの表示 |
| Macro | ページ全体またはセクションのトランジション | 400–600ms | ease-in-out | ルートのトランジション、ヒーローセクション、オンボーディング |
**目安:**迷ったら Micro を使います。アニメーションを過剰に使うより、使わないほうがよいです。
ステップ 1:コンポーネントを監査する
生成されたコンポーネントファイルを読みます。それぞれについて、次の要素を特定します。
- インタラクティブ要素:Micro ティアが必要な要素(ボタン、リンク、入力欄、トグル、
onClickを持つカード) - 表示される要素:Meso ティアが有効な要素(ページセクション、カードグリッド、サイドバー、モーダル、ドロワー、トースト)
- ヒーローまたはランドマーク要素:Macro ティアが適切な要素(主要な見出し、注目画像、ページレベルのトランジション)
目的が明確な要素だけをアニメーションさせます。要素がアニメーションする 理由 を一文で説明できない場合は、アニメーションさせないでください。
ステップ 2:フレームワークを検出し、アニメーションの方式を選択する
フレームワークを判定するために package.json を読み、対応する方式を使います。
| フレームワーク | 方式 |
|---|---|
| Next.js / React | CSS + 必要に応じて Framer Motion |
| SvelteKit / Svelte | Svelte 組み込みのトランジション + CSS |
| Vanilla HTML | CSS のみ |
方式 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:既存コンポーネントにアニメーションを適用する
既存のファイルを変更する場合:
- 各コンポーネントのファイルを最初に読みます — 現在の構造を把握します
- Micro 階層にのみ CSS クラスを追加します(Micro ではコンポーネントのロジックを絶対に変更しないでください)
- React ではモーションコンポーネントでラップし、Meso/Macro の場合は Svelte ではトランジションディレクティブを追加します
- メインの CSS ファイルに視差モーションのオーバーライドを追加します(まだない場合)
- アニメーションあり・なしの両方の状態をテストします — ブラウザーの 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 コンポーネント向けの、コピーしてすぐ使えるパターン集