mcpField
A
animejs-mastery

animejs-mastery

github.com/frogodyデザイン・UI45 回インストール
リポジトリ

animejs-mastery について

animejs-masteryは、コードベースを調査し、anime.jsで画面アニメーションを実装する自律型エージェントスキルです。機会の特定から共通ユーティリティやReact hooksの作成、パフォーマンスと動きの軽減設定の確認までを扱います。

主な機能

  1. 01コードベースとフレームワークの分析
  2. 02アニメーション候補の検出
  3. 03共通アニメーションユーティリティ
  4. 04再利用可能なReact hooks
  5. 05動きの軽減設定と性能の確認

活用シーン

  1. 01画面遷移やローディングアニメーションの追加
  2. 02リスト、モーダル、フォーム応答のアニメーション
  3. 03ボタンやカードの操作効果の作成

インストール

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

$npx skills add https://github.com/frogody/app.isyncso --skill animejs-mastery

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
api-reference.md
patterns.md
scripts/
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:実装チェックリスト

コンポーネントを順番に確認します。各コンポーネントについて、次の作業を行います。

  1. アニメーションの機会を特定する(表示、インタラクション、フィードバック)
  2. 必要なインポートを追加する
  3. ユーティリティ/フックを使用して実装する
  4. 動きを減らす設定での動作をテストする
  5. 60fps のパフォーマンスを確認する

実装順序:

  1. アニメーションユーティリティファイル
  2. React フック(React プロジェクトの場合)
  3. P0:ページ遷移、ローディング状態
  4. P1:リスト、モーダル、フォームのフィードバック
  5. P2:ボタン、カード
  6. 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、translateZ
  • scale、rotate
  • opacity

アニメーションしないもの(レイアウトを引き起こすもの):

  • width、height
  • padding、margin
  • top、left、right、bottom

タイミングの目安:

種類再生時間イージング
マイクロインタラクション100-200mseaseOutQuad
ボタンのフィードバック150mseaseInOutQuad
モーダルの表示200-250mseaseOutQuad
リストの段階表示50-100ms 間隔easeOutQuad
ページ遷移300mseaseOutQuint

出力形式

実装後、次の形式で概要を提示します。

## 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

コメント