motion-principles について
motion-principlesは、Claude Code、claude.ai、Cowork向けのクリエイティブコーディングスキルで、Web、Android、Appleのモーションとインタラクション設計を扱います。時間設定、イージング、アクセシビリティ、パフォーマンスの指針と、各プラットフォームの詳細スキルを提供します。
主な機能
- 01クロスプラットフォームのモーション指針
- 02時間とイージングの推奨
- 03視覚効果の軽減に関するアクセシビリティ指針
- 04アニメーション性能チェックリスト
- 05プラットフォーム別スキルへの案内
活用シーン
- 01UIトランジションの挙動設計
- 02視覚効果の軽減設定への対応確認
- 03アニメーションの滑らかさと性能の改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ athevon/ genjutsu --skill motion-principlesインストール後、Claude Code で/motion-principlesを入力して読み込まれたか確認します。
詳細
バージョン依存です。 以下のすべての API 名、SDK の要件、ブラウザーサポートに関する記述は、 2026-09-08 に一次資料と照合して確認済みです。照合した対象と時期については、
_jutsu/VERSIONS.mdをご覧ください。この日付が古い場合は、バージョン番号に基づいて行動する前に再確認してください。
モーションの原則
基礎となる原則です。すべてのクリエイティブスキルの呼び出し時に読み込まれます。 ここでは簡潔なルールを説明します。詳しくは
references/をご覧ください。
タイミングのルール
| コンテキスト | 時間 | 理由 |
|---|---|---|
| マイクロインタラクション(切り替え、ホバー、フォーカス) | 100-150ms | 即座にフィードバックし、遅延を感じさせません |
| UI の遷移(モーダル、ドロワー、タブ切り替え) | 200-300ms | 滑らかで、もたつきません |
| ページ/ルートの遷移 | 300-500ms | 空間的なストーリーを生み出します |
| スクロール連動/3D | 自由(進行状況に基づく) | ユーザー入力に連動するため、固定の時間はありません |
頻度のルール: アニメーションが頻繁に再生されるほど、短く控えめにする必要があります。 ボタンのホバー(1日1000回)=不透明度の変化を100ms。オンボーディングの表示(生涯で1回)=600ms以上の完全な演出。
イージング早見表
| 動作 | イージング | 理由 |
|---|---|---|
| 要素が入る | ease-out / スプリング | 静止位置に向けて減速し、自然に収まります |
| 要素が出る | ease-in | 離れるときに加速し、邪魔にならないようにします |
| 要素が状態間を移動する | ease-in-out | 滑らかに開始し、滑らかに停止します |
| スクロール連動 | linear / none | 入力と1対1で連動し、遅延を感じさせません |
| 弾む/遊び心のある動き | スプリング(不足減衰) | オーバーシュートによって生き生きとした動きになります |
| きびきびした UI | cubic-bezier(0.2, 0, 0, 1) | 速く始まり、滑らかに収まります |
退出は常に、出現より控えめにします。 出現:300msの ease-out、完全な演出。退出:200msの ease-in、不透明度のみ。
ネイティブのイージング相当値(クロスプラットフォーム)
| Web(CSS / JS) | SwiftUI | Compose |
|---|---|---|
cubic-bezier(0.2, 0, 0, 1) | .spring(response: 0.4, dampingFraction: 0.85) または .snappy | spring(stiffness = Spring.StiffnessMedium, dampingRatio = 0.85f) |
ease-out | .easeOut(duration: 0.3) | tween(durationMillis = 300, easing = LinearOutSlowInEasing) |
ease-in | .easeIn(duration: 0.2) | tween(durationMillis = 200, easing = FastOutLinearInEasing) |
| スプリング(弾む) | .bouncy(iOS 17+) | spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy) |
| スプリング(滑らか) | .smooth(iOS 17+) | spring(stiffness = Spring.StiffnessMedium, dampingRatio = Spring.DampingRatioNoBouncy) |
アクセシビリティ(必須)
モーションの軽減 - 必須
すべてのアニメーションコンポーネントは、ユーザーのモーション軽減設定に従う必要があります。プラットフォームを問わず、例外はありません。
| プラットフォーム | API |
|---|---|
| Web CSS | @media (prefers-reduced-motion: reduce) |
| Web JS | window.matchMedia('(prefers-reduced-motion: reduce)') |
| SwiftUI | @Environment(\.accessibilityReduceMotion) var reduceMotion |
| UIKit | UIAccessibility.isReduceMotionEnabled(および reduceMotionStatusDidChangeNotification) |
| Compose | Settings.Global.ANIMATOR_DURATION_SCALE を使ったカスタムヘルパー(詳しくは mobile-principles/references/accessibility-mobile.md を参照) |
CSS:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
SwiftUI:
struct AnimatedView: View {
@Environment(\.accessibilityReduceMotion) var reduceMotion
@State var visible = false
var body: some View {
Text("Hello")
.opacity(visible ? 1 : 0)
.animation(reduceMotion ? .none : .spring(response: 0.4, dampingFraction: 0.85), value: visible)
}
}
Compose:
@Composable
fun rememberReduceMotion(): Boolean {
val context = LocalContext.current
return remember {
Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f
}
}
@Composable
fun AnimatedComponent(visible: Boolean) {
val reduce = rememberReduceMotion()
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = if (reduce) snap() else spring(stiffness = Spring.StiffnessMedium)
)
}
その他の a11y 要件
- フォーカスインジケーターをアニメーションで隠してはいけません
- アニメーションコンテンツは、すべてのフレームで WCAG のコントラスト比を満たす必要があります(遷移中にテキストがフェードして見えなくなることがないようにします)
- ループアニメーションには、一時停止する手段を用意してください
あらゆる場面での「禁止」ルール
1. width/height/top/left はアニメーションさせない
フレームごとにレイアウトの再計算が発生し、カクつきます。
/* BAD */
.drawer { transition: height 0.3s ease; }
.drawer.open { height: 400px; }
/* GOOD */
.drawer { transition: transform 0.3s ease-out; transform: translateY(100%); }
.drawer.open { transform: translateY(0); }
SwiftUI 相当(BAD と GOOD):
// BAD - animates frame (causes layout pass)
.frame(height: open ? 400 : 0)
.animation(.easeInOut, value: open)
// GOOD - animates transform via offset
.offset(y: open ? 0 : 400)
.animation(.spring(), value: open)
Compose 相当(BAD と GOOD):
// BAD - animates size (full layout pass)
val height by animateDpAsState(if (open) 400.dp else 0.dp)
// GOOD - animates Y offset via graphicsLayer
val translation by animateFloatAsState(if (open) 0f else 400f)
Box(modifier = Modifier.graphicsLayer { translationY = translation })
2. scale を 0 にしない
scale(0) を使うと、要素がブラックホールに消えるように見えます。必ず最小値を保ってください。
/* BAD */
.modal-exit { transform: scale(0); }
/* GOOD */
.modal-exit { transform: scale(0.95); opacity: 0; }
SwiftUI 相当:
// BAD
.scaleEffect(visible ? 1.0 : 0.0)
// GOOD
.scaleEffect(visible ? 1.0 : 0.95)
.opacity(visible ? 1.0 : 0.0)
Compose 相当:
// BAD
Modifier.graphicsLayer { scaleX = if (visible) 1f else 0f; scaleY = if (visible) 1f else 0f }
// GOOD
Modifier.graphicsLayer {
scaleX = if (visible) 1f else 0.95f
scaleY = if (visible) 1f else 0.95f
alpha = if (visible) 1f else 0f
}
3. 出現時に ease-in を使わない
Ease-in は開始が遅くなります。出現する要素がもたつくと、壊れているように感じられます。
/* BAD */
.card-enter { animation: fadeIn 0.3s ease-in; }
/* GOOD */
.card-enter { animation: fadeIn 0.3s ease-out; }
/* OR spring via JS for natural feel */
4. UI 操作の応答時間は500msを超えないようにする
モーダル、ドロップダウン、ツールチップ、タブなど、ユーザーは待っています。ユーザーの時間を大切にしましょう。
// BAD
gsap.to(modal, { opacity: 1, y: 0, duration: 0.8 });
// GOOD
gsap.to(modal, { opacity: 1, y: 0, duration: 0.25, ease: "power2.out" });
SwiftUI:
// BAD
.animation(.easeInOut(duration: 0.8), value: state)
// GOOD
.animation(.spring(response: 0.25, dampingFraction: 0.85), value: state)
Compose:
// BAD
animateContentSize(animationSpec = tween(800))
// GOOD
animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessMediumLow))
5. prefers-reduced-motion を必ず考慮する
これはアクセシビリティ要件であり、あればよいというものではありません。
// BAD
gsap.from('.hero-title', { opacity: 0, y: 40, duration: 0.6 });
// GOOD
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReduced) {
gsap.from('.hero-title', { opacity: 0, y: 40, duration: 0.6 });
}
SwiftUI/Compose に相当する機能については、上記のクロスプラットフォームの「動きを減らす」セクションを参照してください。
パフォーマンスチェックリスト
- アニメーションには
transformとopacity(コンポジットのみのプロパティ)だけを使用する will-changeは控えめに使用し、アニメーションの完了後に削除する- JS アニメーションでは、
setTimeout/setIntervalよりrequestAnimationFrameを優先する - スクロール駆動の場合:CSS の
animation-timeline> JS のIntersectionObserver> スクロールリスナー - ローエンドデバイスでテストする(DevTools で CPU を4倍にスロットルする)
クイックリファレンス:サブスキルの読み込み
| 必要な内容 | 読み込むファイル |
|---|---|
| イージングの詳細、スプリング設定 | references/easing-guide.md |
| コピーして使える入場/退出パターン | references/enter-exit-recipes.md |
| デザイナー向けのスタイル選択 | references/designers.md |
| モバイル UX のコンテキスト | ../mobile-principles/SKILL.md |
| デスクトップ UX のコンテキスト | ../desktop-principles/SKILL.md |
| GSAP の詳細 | ../gsap/SKILL.md |
| Framer Motion の詳細 | ../framer-motion/SKILL.md |
| CSS のみのアニメーション | ../css-native/SKILL.md |
| Three.js/R3F | ../threejs-r3f/SKILL.md |
| Canvas/ジェネレーティブ | ../canvas-generative/SKILL.md |
| Compose の Android アニメーション | ../compose-motion/SKILL.md |
| Compose Multiplatform | ../compose-multiplatform/SKILL.md |
| SwiftUI の iOS/macOS アニメーション | ../swiftui-motion/SKILL.md |
| Compose の高度なグラフィックス | ../compose-graphics/SKILL.md |
| SwiftUI の高度なグラフィックス | ../swiftui-graphics/SKILL.md |
| ビジュアル/モーション/アクセシビリティの監査 | ../design-audit/SKILL.md |
| UI/UX インテリジェンス(84種類のスタイル、192種類のパレット) | ../ui-ux-pro-max/SKILL.md |