animejs-mastery について
animejs-masteryは、コードベースを調査し、anime.jsで画面アニメーションを実装する自律型エージェントスキルです。機会の特定から共通ユーティリティやReact hooksの作成、パフォーマンスと動きの軽減設定の確認までを扱います。
主な機能
- 01コードベースとフレームワークの分析
- 02アニメーション候補の検出
- 03共通アニメーションユーティリティ
- 04再利用可能なReact hooks
- 05動きの軽減設定と性能の確認
活用シーン
- 01画面遷移やローディングアニメーションの追加
- 02リスト、モーダル、フォーム応答のアニメーション
- 03ボタンやカードの操作効果の作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ frogody/ app.isyncso --skill animejs-masteryインストール後、Claude Code で/animejs-masteryを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/api-reference.mdpatterns.mdscripts/analyze-for-animations.sh詳細
Anime.js アニメーションアップグレードエージェント
コードベースを分析し、anime.js を使用して洗練された高パフォーマンスのアニメーションを体系的に実装する自律型エージェントです。
エージェントのワークフロー
以下のフェーズを順番に実行します。自律的に作業し、各ステップの間で許可を求めないでください。
フェーズ 1:コードベースの分析
# 1. Identify framework and structure
find . -name "package.json" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.vue" -o -name "*.svelte" | head -20
# 2. Check if anime.js is installed
grep -r "animejs\|anime.js" package.json 2>/dev/null || echo "NOT_INSTALLED"
# 3. Map component structure
find . -type f \( -name "*.tsx" -o -name "*.jsx" -o -name "*.vue" \) -path "*/components/*" | head -50
anime.js がインストールされていない場合はインストールします:
npm install animejs
# For TypeScript projects:
npm install --save-dev @types/animejs
フェーズ 2:実装機会の特定
コードベースをスキャンし、次のような効果の高いアニメーションの機会を探します。
| 優先度 | 探すパターン | アニメーションの種類 |
|---|---|---|
| P0 | ページ/ルートの遷移 | フェード+スライドのシーケンス |
| P0 | ローディング状態、スピナー | 滑らかなパルス/回転 |
| P1 | データを表示するリスト | 段階的な表示 |
| P1 | モーダル/ダイアログ/ドロワー | スケール+フェードで表示 |
| P1 | フォームの送信 | 成功/エラーのフィードバック |
| P2 | ボタン、CTA | ホバー/押下時のマイクロインタラクション |
| P2 | カード、タイル | ホバー時の浮き上がり効果 |
| P2 | データの可視化 | 数値のカウントアップ、チャートの表示 |
| P3 | アイコン、バッジ | 注意を促すパルス |
| P3 | ツールチップ、ポップオーバー | ソフトな表示 |
検索パターン:
# Find loading states
grep -rn "loading\|isLoading\|skeleton\|Spinner" --include="*.tsx" --include="*.jsx"
# Find modals/dialogs
grep -rn "Modal\|Dialog\|Drawer\|Sheet" --include="*.tsx" --include="*.jsx"
# Find lists with map
grep -rn "\.map(" --include="*.tsx" --include="*.jsx" | grep -i "item\|card\|row"
# Find buttons
grep -rn "<button\|<Button\|onClick" --include="*.tsx" --include="*.jsx"
# Find route transitions
grep -rn "Route\|router\|navigate\|Link" --include="*.tsx" --include="*.jsx"
フェーズ 3:アニメーションユーティリティの作成
まず、共有アニメーションユーティリティファイルを作成します。
// src/lib/animations.ts (or utils/animations.ts)
import anime from 'animejs';
// Respect user preferences
export const prefersReducedMotion = () =>
typeof window !== 'undefined' &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Safe animate wrapper
export const safeAnimate = (
targets: anime.AnimeParams['targets'],
params: Omit<anime.AnimeParams, 'targets'>
) => {
if (prefersReducedMotion()) {
return anime({ targets, ...params, duration: 0 });
}
return anime({ targets, ...params });
};
// Staggered list entrance
export const staggerIn = (selector: string, delay = 50) =>
safeAnimate(selector, {
translateY: [20, 0],
opacity: [0, 1],
delay: anime.stagger(delay),
duration: 400,
easing: 'easeOutQuad',
});
// Modal entrance
export const modalIn = (selector: string) =>
safeAnimate(selector, {
scale: [0.95, 1],
opacity: [0, 1],
duration: 200,
easing: 'easeOutQuad',
});
// Button press feedback
export const buttonPress = (element: HTMLElement) =>
safeAnimate(element, {
scale: [1, 0.97, 1],
duration: 150,
easing: 'easeInOutQuad',
});
// Success animation
export const successPop = (selector: string) =>
safeAnimate(selector, {
scale: [0, 1.1, 1],
opacity: [0, 1],
duration: 400,
easing: 'easeOutBack',
});
// Error shake
export const errorShake = (selector: string) =>
safeAnimate(selector, {
translateX: [0, -8, 8, -8, 8, -4, 4, 0],
duration: 400,
easing: 'easeInOutQuad',
});
// Fade out (for exits)
export const fadeOut = (selector: string) =>
safeAnimate(selector, {
opacity: [1, 0],
translateY: [0, -10],
duration: 200,
easing: 'easeInQuad',
});
// Number counter
export const countUp = (
element: HTMLElement,
endValue: number,
duration = 1000
) => {
const obj = { value: 0 };
return anime({
targets: obj,
value: endValue,
round: 1,
duration,
easing: 'easeOutExpo',
update: () => {
element.textContent = obj.value.toLocaleString();
},
});
};
// Skeleton pulse
export const skeletonPulse = (selector: string) =>
safeAnimate(selector, {
opacity: [0.5, 1, 0.5],
duration: 1500,
loop: true,
easing: 'easeInOutSine',
});
フェーズ 4:React フックのパターン
React プロジェクト用の再利用可能なフックを作成します。
// src/hooks/useAnimation.ts
import { useEffect, useRef, useCallback } from 'react';
import anime from 'animejs';
import { prefersReducedMotion } from '@/lib/animations';
export function useStaggeredEntrance<T extends HTMLElement>(
deps: unknown[] = []
) {
const containerRef = useRef<T>(null);
useEffect(() => {
if (!containerRef.current || prefersReducedMotion()) return;
const children = containerRef.current.children;
anime({
targets: children,
translateY: [20, 0],
opacity: [0, 1],
delay: anime.stagger(50),
duration: 400,
easing: 'easeOutQuad',
});
}, deps);
return containerRef;
}
export function useButtonFeedback() {
const handlePress = useCallback((e: React.MouseEvent<HTMLElement>) => {
if (prefersReducedMotion()) return;
anime({
targets: e.currentTarget,
scale: [1, 0.97, 1],
duration: 150,
easing: 'easeInOutQuad',
});
}, []);
return { onMouseDown: handlePress };
}
export function useHoverLift<T extends HTMLElement>() {
const ref = useRef<T>(null);
useEffect(() => {
const el = ref.current;
if (!el || prefersReducedMotion()) return;
const enter = () => {
anime.remove(el);
anime({
targets: el,
translateY: -4,
boxShadow: '0 8px 30px rgba(0,0,0,0.12)',
duration: 200,
easing: 'easeOutQuad',
});
};
const leave = () => {
anime.remove(el);
anime({
targets: el,
translateY: 0,
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
duration: 200,
easing: 'easeOutQuad',
});
};
el.addEventListener('mouseenter', enter);
el.addEventListener('mouseleave', leave);
return () => {
el.removeEventListener('mouseenter', enter);
el.removeEventListener('mouseleave', leave);
};
}, []);
return ref;
}
export function useCountUp(endValue: number, duration = 1000) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
if (!ref.current) return;
const obj = { value: 0 };
anime({
targets: obj,
value: endValue,
round: 1,
duration: prefersReducedMotion() ? 0 : duration,
easing: 'easeOutExpo',
update: () => {
if (ref.current) {
ref.current.textContent = obj.value.toLocaleString();
}
},
});
}, [endValue, duration]);
return ref;
}
フェーズ 5:実装チェックリスト
コンポーネントを順番に確認します。各コンポーネントについて、次の作業を行います。
- アニメーションの機会を特定する(表示、インタラクション、フィードバック)
- 必要なインポートを追加する
- ユーティリティ/フックを使用して実装する
- 動きを減らす設定での動作をテストする
- 60fps のパフォーマンスを確認する
実装順序:
- アニメーションユーティリティファイル
- React フック(React プロジェクトの場合)
- P0:ページ遷移、ローディング状態
- P1:リスト、モーダル、フォームのフィードバック
- P2:ボタン、カード
- P3:アイコン、ツールチップ
フェーズ 6:一般的な実装パターン
段階的に表示するリストコンポーネント:
function ItemList({ items }) {
const listRef = useStaggeredEntrance<HTMLUListElement>([items]);
return (
<ul ref={listRef}>
{items.map(item => (
<li key={item.id} style={{ opacity: 0 }}>
{item.name}
</li>
))}
</ul>
);
}
押下時のフィードバックがあるボタン:
function AnimatedButton({ children, onClick, ...props }) {
const feedbackProps = useButtonFeedback();
return (
<button {...props} {...feedbackProps} onClick={onClick}>
{children}
</button>
);
}
ホバー時に浮き上がるカード:
function Card({ children }) {
const cardRef = useHoverLift<HTMLDivElement>();
return (
<div ref={cardRef} className="card">
{children}
</div>
);
}
表示アニメーションのあるモーダル:
function Modal({ isOpen, children }) {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen && modalRef.current) {
modalIn(modalRef.current);
}
}, [isOpen]);
if (!isOpen) return null;
return (
<div ref={modalRef} className="modal" style={{ opacity: 0 }}>
{children}
</div>
);
}
パフォーマンスに関するルール
必ずアニメーションするもの(GPU アクセラレーション):
translateX、translateY、translateZscale、rotateopacity
アニメーションしないもの(レイアウトを引き起こすもの):
width、heightpadding、margintop、left、right、bottom
タイミングの目安:
| 種類 | 再生時間 | イージング |
|---|---|---|
| マイクロインタラクション | 100-200ms | easeOutQuad |
| ボタンのフィードバック | 150ms | easeInOutQuad |
| モーダルの表示 | 200-250ms | easeOutQuad |
| リストの段階表示 | 50-100ms 間隔 | easeOutQuad |
| ページ遷移 | 300ms | easeOutQuint |
出力形式
実装後、次の形式で概要を提示します。
## Animation Upgrade Summary
### Files Created
- `src/lib/animations.ts` - Core utilities
- `src/hooks/useAnimation.ts` - React hooks
### Components Updated
| Component | Animation Added | Priority |
|-----------|-----------------|----------|
| ItemList | Staggered entrance | P1 |
| Modal | Scale + fade in | P1 |
| Button | Press feedback | P2 |
### Performance Notes
- All animations use transform/opacity only
- Reduced motion respected globally
- Average animation duration: 200ms
参考ファイル
詳細な API ドキュメント:references/api-reference.md
追加のパターン:references/patterns.md