mcpField
M
mastering-animate-presence

mastering-animate-presence

github.com/raphaelsalajaデザイン・UI936147 回インストール33 週間前に更新
リポジトリ

mastering-animate-presence について

mastering-animate-presenceは、MotionのAnimatePresenceの使い方をレビューするスキルです。退出アニメーション、presenceフック、モード、入れ子の退出を確認し、ファイル名と行番号付きで指摘を出します。

主な機能

  1. 01AnimatePresenceのラッパーとexitプロパティを確認
  2. 02動的リストのキーをレビュー
  3. 03presenceフックの使い方を検証
  4. 04アニメーションモードとレイアウト動作を確認
  5. 05入れ子の退出の連携をレビュー

活用シーン

  1. 01Motionの退出アニメーションコードをレビュー
  2. 02exitプロパティやリストキーの不足を発見
  3. 03usePresenceのクリーンアップ処理を確認
  4. 04リストのレイアウトアニメーション競合を調査

インストール

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

$npx skills add https://github.com/raphaelsalaja/userinterface-wiki --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

参考資料

コメント