to-spring-or-not-to-spring について
操作の種類に応じたタイミング規則でアニメーションコードを確認するスキルです。ファイルの行番号とルール ID 付きで指摘を得たい開発者向けです。
主な機能
- 01ジェスチャー動作のスプリング確認
- 02用途に応じたイージングの確認
- 03時間範囲と上限のチェック
- 04アニメーションを省く場面の特定
- 05ファイル・行番号・ルール ID で報告
活用シーン
- 01ドラッグやスワイプのアニメーション確認
- 02モーダルや画面遷移の曲線確認
- 03UI コードのアニメーション時間監査
- 04入力やキーボード操作時の動きを検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ raphaelsalaja/ skill --skill to-spring-or-not-to-springインストール後、Claude Code で/to-spring-or-not-to-springを入力して読み込まれたか確認します。
詳細
スプリングを使うべきか、使わないべきか
インタラクションの種類に応じて、アニメーションコードで適切なタイミング関数が選択されているかを確認します。
仕組み
- 指定されたファイルを読み込みます(またはユーザーにファイル/パターンを尋ねます)
- 以下のすべてのルールに照らして確認します
file:line形式で指摘事項を出力します
ルールのカテゴリ
| 優先度 | カテゴリ | プレフィックス |
|---|---|---|
| 1 | スプリングの選択 | spring- |
| 2 | イージングの選択 | easing- |
| 3 | 継続時間 | duration- |
| 4 | アニメーションなし | none- |
判断の枠組み
次の点を確認します。この動きはユーザーへの反応ですか、それともシステムからの働きかけですか?
| 動きの種類 | 最適な選択 | 理由 |
|---|---|---|
| ユーザー主導(ドラッグ、フリック、ジェスチャー) | スプリング | 中断されても自然に続き、速度を保ちます |
| システム主導(状態変化、フィードバック) | イージング | 開始と終了が明確で、タイミングが予測しやすくなります |
| 時間の表現(進捗、読み込み) | リニア | 時間と進捗が1対1で対応します |
| 高頻度(タイピング、高速な切り替え) | なし | アニメーションはノイズになり、動作が遅く感じられます |
ルール
スプリングの選択ルール
spring-for-gestures
ジェスチャー主導の動き(ドラッグ、フリック、スワイプ)には、スプリングを使用する必要があります。
不合格:
<motion.div
drag="x"
transition={{ duration: 0.3, ease: "easeOut" }}
/>
合格:
<motion.div
drag="x"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
spring-for-interruptible
中断される可能性のある動きには、スプリングを使用する必要があります。
不合格:
// User can click again mid-animation
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ duration: 0.3 }}
/>
合格:
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>
spring-preserves-velocity
速度が重要な場合は、入力のエネルギーを保つためにスプリングを使用します。
不合格:
// Fast flick and slow flick animate identically
onDragEnd={(e, info) => {
animate(target, { x: 0 }, { duration: 0.3 });
}}
合格:
// Fast flick moves faster than slow flick
onDragEnd={(e, info) => {
animate(target, { x: 0 }, {
type: "spring",
velocity: info.velocity.x,
});
}}
spring-params-balanced
スプリングのパラメーターはバランスを取る必要があります。過剰な振動は避けてください。
不合格:
transition={{
type: "spring",
stiffness: 1000,
damping: 5, // Too low - excessive bounce
}}
合格:
transition={{
type: "spring",
stiffness: 500,
damping: 30, // Balanced - settles quickly
}}
イージングの選択ルール
easing-for-state-change
システムが開始する状態変化には、イージングカーブを使用します。
不合格:
// Toast notification using spring
<motion.div
animate={{ y: 0 }}
transition={{ type: "spring" }}
/>
// Feels restless for a simple announcement
合格:
<motion.div
animate={{ y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>
easing-entrance-ease-out
登場時にはイーズアウトを使用します(素早く現れ、ゆっくり落ち着きます)。
不合格:
.modal-enter {
animation-timing-function: ease-in;
}
合格:
.modal-enter {
animation-timing-function: ease-out;
}
easing-exit-ease-in
退場時にはイーズインを使用します(離れる前に勢いを増します)。
不合格:
.modal-exit {
animation-timing-function: ease-out;
}
合格:
.modal-exit {
animation-timing-function: ease-in;
}
easing-transition-ease-in-out
ビュー/モードの切り替えには、注意を中立的に引きつけるイーズインアウトを使用します。
合格:
.page-transition {
animation-timing-function: ease-in-out;
}
easing-linear-only-progress
リニアイージングは、プログレスバーと時間の表現にのみ使用します。
不合格:
.card-slide {
transition: transform 200ms linear; /* Mechanical feel */
}
合格:
.progress-bar {
transition: width 100ms linear; /* Honest time representation */
}
継続時間のルール
duration-press-hover
押下およびホバーのインタラクション:120~180ms。
不合格:
.button:hover {
transition: background-color 400ms;
}
合格:
.button:hover {
transition: background-color 150ms;
}
duration-small-state
小さな状態変化:180~260ms。
合格:
.toggle {
transition: transform 200ms ease;
}
duration-max-300ms
ユーザーが開始するアニメーションは、300msを超えてはいけません。
不合格:
<motion.div transition={{ duration: 0.5 }} />
合格:
<motion.div transition={{ duration: 0.25 }} />
duration-shorten-before-curve
アニメーションが遅く感じられる場合は、カーブを調整する前に継続時間を短くします。
不合格(よくある間違い):
/* Trying to fix slowness with sharper curve */
.element {
transition: 400ms cubic-bezier(0, 0.9, 0.1, 1);
}
合格:
/* Fix slowness with shorter duration */
.element {
transition: 200ms ease-out;
}
アニメーションなしのルール
none-high-frequency
高頻度のインタラクションには、アニメーションを使用しません。
不合格:
// Animated on every keystroke
function SearchInput() {
return (
<motion.div animate={{ scale: [1, 1.02, 1] }}>
<input onChange={handleSearch} />
</motion.div>
);
}
合格:
function SearchInput() {
return <input onChange={handleSearch} />;
}
none-keyboard-navigation
キーボード操作によるナビゲーションは、アニメーションなしで即座に行います。
不合格:
function Menu() {
return items.map(item => (
<motion.li
whileFocus={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
/>
));
}
合格:
function Menu() {
return items.map(item => (
<li className={styles.menuItem} /> // CSS :focus-visible only
));
}
none-context-menu-entrance
コンテキストメニューの登場時にはアニメーションを使用しません(退場時のみ)。
不合格:
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}
/>
合格:
<motion.div exit={{ opacity: 0, scale: 0.95 }} />
出力形式
ファイルを確認するときは、指摘事項を次の形式で出力します。
file:line - [rule-id] description of issue
Example:
components/drawer/index.tsx:45 - [spring-for-gestures] Drag interaction using easing instead of spring
components/modal/styles.module.css:23 - [easing-entrance-ease-out] Modal entrance using ease-in
まとめ表
指摘事項の後に、次のまとめを出力します。
| ルール | 件数 | 重大度 |
|---|---|---|
spring-for-gestures | 2 | HIGH |
easing-entrance-ease-out | 1 | MEDIUM |
duration-max-300ms | 3 | MEDIUM |
クイックリファレンス
| インタラクション | タイミング | 種類 |
|---|---|---|
| ドラッグのリリース | スプリング | stiffness: 500, damping: 30 |
| ボタンの押下 | 150ms | ease |
| モーダルの登場 | 200ms | ease-out |
| モーダルの退場 | 150ms | ease-in |
| ページの切り替え | 250ms | ease-in-out |
| プログレスバー | 可変 | linear |
| タイピングのフィードバック | 0ms | なし |