mcpField
M
mastering-animate-presence

mastering-animate-presence

github.com/raphaelsalajaデザイン・UI42325 回インストール今日更新
リポジトリ

mastering-animate-presence について

Motion コードの AnimatePresence と退出アニメーションの実践を確認するエージェントスキルです。アニメーション UI を保守する開発者向けに、ルール違反をファイル名と行番号で示し、集計します。

主な機能

  1. 01AnimatePresence のラップと exit プロパティの確認
  2. 02リストキーとアニメーション対称性の確認
  3. 03Presence Hooks の呼び出し位置の確認
  4. 04モード選択とレイアウト競合の確認
  5. 05ネストした退出と操作制御の確認
  6. 06ファイル・行番号付きの指摘と集計

活用シーン

  1. 01条件付き要素の退出アニメーションのレビュー
  2. 02動的リストのアニメーション設定の確認
  3. 03usePresence の非同期クリーンアップ問題の特定
  4. 04ネストしたアニメーションのタイミング評価

インストール

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

$npx skills add https://github.com/raphaelsalaja/skill --skill mastering-animate-presence

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

詳細

AnimatePresence を使いこなす

AnimatePresence と終了アニメーションのベストプラクティスに沿って、Motion のコードをレビューします。

仕組み

  1. 指定されたファイルを読み込みます(または、ファイルやパターンをユーザーに確認します)
  2. 以下のすべてのルールに照らして確認します
  3. file:line 形式で指摘事項を出力します

ルールのカテゴリ

優先度カテゴリプレフィックス
1終了アニメーションexit-
2Presence フックpresence-
3モードの選択mode-
4ネストした終了アニメーションnested-

ルール

終了アニメーションのルール
exit-requires-wrapper

条件付きのモーション要素は AnimatePresence でラップする必要があります。

失敗例:

{isVisible && (
  <motion.div exit={{ opacity: 0 }} />
)}

成功例:

<AnimatePresence>
  {isVisible && (
    <motion.div exit={{ opacity: 0 }} />
  )}
</AnimatePresence>
exit-prop-required

AnimatePresence 内の要素には exit プロパティを定義する必要があります。

失敗例:

<AnimatePresence>
  {isOpen && (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
  )}
</AnimatePresence>

成功例:

<AnimatePresence>
  {isOpen && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>
exit-key-required

AnimatePresence 内の動的リストには一意のキーが必要です。

失敗例:

<AnimatePresence>
  {items.map((item, index) => (
    <motion.div key={index} exit={{ opacity: 0 }} />
  ))}
</AnimatePresence>

成功例:

<AnimatePresence>
  {items.map((item) => (
    <motion.div key={item.id} exit={{ opacity: 0 }} />
  ))}
</AnimatePresence>
exit-matches-initial

対称性を保つため、終了アニメーションは初期状態を反映する必要があります。

失敗例:

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ scale: 0 }}
/>

成功例:

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: 20 }}
/>
Presence フックのルール
presence-hook-in-child

useIsPresent は AnimatePresence の親ではなく、子から呼び出す必要があります。

失敗例:

function Parent() {
  const isPresent = useIsPresent(); // Wrong location
  return (
    <AnimatePresence>
      {show && <Child />}
    </AnimatePresence>
  );
}

成功例:

function Child() {
  const isPresent = useIsPresent(); // Correct location
  return <motion.div data-exiting={!isPresent} />;
}
presence-safe-to-remove

usePresence を使う場合、非同期処理の後に必ず safeToRemove を呼び出してください。

失敗例:

function AsyncComponent() {
  const [isPresent, safeToRemove] = usePresence();

  useEffect(() => {
    if (!isPresent) {
      cleanup(); // Never calls safeToRemove
    }
  }, [isPresent]);
}

成功例:

function AsyncComponent() {
  const [isPresent, safeToRemove] = usePresence();

  useEffect(() => {
    if (!isPresent) {
      cleanup().then(safeToRemove);
    }
  }, [isPresent, safeToRemove]);
}
presence-disable-interactions

isPresent を使って、終了中の要素のインタラクションを無効にしてください。

失敗例:

function Card() {
  const isPresent = useIsPresent();
  return <button onClick={handleClick}>Click</button>;
  // Button clickable during exit
}

成功例:

function Card() {
  const isPresent = useIsPresent();
  return (
    <button onClick={handleClick} disabled={!isPresent}>
      Click
    </button>
  );
}
モード選択のルール
mode-wait-doubles-duration

モード「wait」ではアニメーション時間がほぼ 2 倍になるため、それに合わせてタイミングを調整してください。

失敗例:

<AnimatePresence mode="wait">
  <motion.div transition={{ duration: 0.3 }} />
</AnimatePresence>
// Total time: ~600ms (too slow)

成功例:

<AnimatePresence mode="wait">
  <motion.div transition={{ duration: 0.15 }} />
</AnimatePresence>
// Total time: ~300ms (acceptable)
mode-sync-layout-conflict

モード「sync」ではレイアウトの競合が発生するため、終了する要素を絶対配置してください。

失敗例:

<AnimatePresence mode="sync">
  {items.map(item => (
    <motion.div exit={{ opacity: 0 }}>{item}</motion.div>
  ))}
</AnimatePresence>
// Exiting and entering elements compete for space

成功例:

<AnimatePresence mode="popLayout">
  {items.map(item => (
    <motion.div exit={{ opacity: 0 }}>{item}</motion.div>
  ))}
</AnimatePresence>
mode-pop-layout-for-lists

リストの並べ替えアニメーションには popLayout モードを使ってください。

失敗例:

<AnimatePresence>
  {items.map(item => <ListItem key={item.id} />)}
</AnimatePresence>
// Layout shifts during exit

成功例:

<AnimatePresence mode="popLayout">
  {items.map(item => <ListItem key={item.id} />)}
</AnimatePresence>
ネストした終了アニメーションのルール
nested-propagate-required

終了アニメーションを連携させるには、ネストした AnimatePresence で propagate プロパティを使う必要があります。

失敗例:

<AnimatePresence>
  {isOpen && (
    <motion.div exit={{ opacity: 0 }}>
      <AnimatePresence>
        {items.map(item => (
          <motion.div key={item.id} exit={{ scale: 0 }} />
        ))}
      </AnimatePresence>
    </motion.div>
  )}
</AnimatePresence>
// Children vanish instantly when parent exits

成功例:

<AnimatePresence propagate>
  {isOpen && (
    <motion.div exit={{ opacity: 0 }}>
      <AnimatePresence propagate>
        {items.map(item => (
          <motion.div key={item.id} exit={{ scale: 0 }} />
        ))}
      </AnimatePresence>
    </motion.div>
  )}
</AnimatePresence>
nested-consistent-timing

親と子の終了時間を調整してください。

失敗例:

// Parent exits in 100ms, children in 500ms
<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.1 }}>
  <motion.div exit={{ scale: 0 }} transition={{ duration: 0.5 }} />
</motion.div>

成功例:

// Parent waits for children or exits simultaneously
<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.2 }}>
  <motion.div exit={{ scale: 0 }} transition={{ duration: 0.15 }} />
</motion.div>

出力形式

ファイルをレビューする際は、次の形式で指摘事項を出力します。

file:line - [rule-id] description of issue

Example:
components/modal/index.tsx:23 - [exit-requires-wrapper] Conditional motion.div not wrapped in AnimatePresence
components/modal/index.tsx:45 - [exit-prop-required] Missing exit prop on motion element

まとめ表

指摘事項の後に、次のまとめを出力します。

ルール件数重大度
exit-requires-wrapper2HIGH
exit-prop-required3HIGH
mode-wait-doubles-duration1MEDIUM

参考資料

コメント