ui-animation について
UI デザイナーや開発者向けのアニメーション設計ガイドです。動きの原則、実装パターン、パフォーマンス、アクセシビリティを扱い、目的に沿った UI アニメーションの設計に役立ちます。
主な機能
- 01動きの原則と時間の目安
- 02イージング関数とアニメーションパターン
- 03CSS と JavaScript の実装例
- 04パフォーマンス指針と FLIP 手法
- 05動きを減らす設定とアクセシビリティへの配慮
活用シーン
- 01ボタンやトグルのマイクロインタラクション設計
- 02ローディング表示やページ遷移の設計
- 03アニメーション性能と動きを減らす設定の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill ui-animationインストール後、Claude Code で/ui-animationを入力して読み込まれたか確認します。
詳細
UIアニメーションとモーションデザイン
ユーザーインターフェースで、目的に沿った高パフォーマンスなアニメーションを作成するための包括的なガイドです。
アニメーションの原則
アニメーションの12原則(UIへの応用)
| 原則 | UIへの応用 |
|---|---|
| タイミング | 重要度や距離を期間に反映します |
| イージング | 自然な加速と減速を表現します |
| 予備動作 | アクションのための視覚的な準備 |
| 後続動作 | 停止後も勢いが続きます |
| 二次アクション | 補助要素が反応します |
| 演出 | 重要な要素に注目を集めます |
| スクワッシュ&ストレッチ | 弾むような楽しいインタラクション |
| 誇張 | 重要なフィードバックを強調します |
| 弧 | 自然な曲線の動きの軌跡 |
| オーバーラップ | 要素が異なる速度で動きます |
| 立体感のある描画 | 一貫した3D空間を保ちます |
| 魅力 | 人を引きつける、心地よい動き |
アニメーションを使う理由
FUNCTIONAL PURPOSES:
✓ Guide attention to important changes
✓ Show relationships between elements
✓ Provide feedback for actions
✓ Communicate system status
✓ Ease cognitive load
✓ Create spatial orientation
NOT FOR:
✗ Pure decoration
✗ Showing off skills
✗ Making things "feel modern"
✗ Distracting from content
タイミングと期間
期間のガイドライン
INSTANT (0-100ms):
└─→ Button state changes
└─→ Toggle switches
└─→ Micro-feedback
FAST (100-200ms):
└─→ Hover effects
└─→ Simple fades
└─→ Small movements
STANDARD (200-300ms):
└─→ Most UI transitions
└─→ Modal open/close
└─→ Dropdown menus
SLOW (300-500ms):
└─→ Complex transitions
└─→ Page transitions
└─→ Large element movement
DELIBERATE (500ms+):
└─→ Hero animations
└─→ Skeleton loading
└─→ Onboarding sequences
距離に基づくタイミング
Rule: Longer distance = Longer duration
Small (< 100px): 150-200ms
Medium (100-300px): 200-300ms
Large (300-500px): 300-400ms
Full screen: 400-500ms
Formula:
duration = baseTime + (distance × factor)
イージング関数
標準的なイージング
LINEAR
├────────────────────────────┤
Constant speed. Rarely natural.
Use: Progress bars, clock hands
EASE-OUT (Deceleration)
├═══════════────────────────┤
Fast start, slow end.
Use: Elements entering the screen
EASE-IN (Acceleration)
├────────────────═══════════┤
Slow start, fast end.
Use: Elements leaving the screen
EASE-IN-OUT (S-curve)
├────═══════════════────────┤
Slow start and end, fast middle.
Use: On-screen transitions
EASE-OUT-BACK (Overshoot)
├═══════════────────────╗───┤
Overshoots, settles back.
Use: Playful entrances, bounces
CSSイージング値
/* Built-in keywords */
linear: cubic-bezier(0, 0, 1, 1)
ease: cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in: cubic-bezier(0.42, 0, 1, 1)
ease-out: cubic-bezier(0, 0, 0.58, 1)
ease-in-out: cubic-bezier(0.42, 0, 0.58, 1)
/* Material Design standard */
standard: cubic-bezier(0.4, 0, 0.2, 1)
decelerate: cubic-bezier(0, 0, 0.2, 1)
accelerate: cubic-bezier(0.4, 0, 1, 1)
/* Custom: Snappy */
snappy: cubic-bezier(0.5, 0, 0, 1)
/* Custom: Bouncy */
bouncy: cubic-bezier(0.68, -0.55, 0.27, 1.55)
/* Spring-like (use JS libraries) */
spring: { stiffness: 300, damping: 20 }
使い分け
| シナリオ | イージング | 理由 |
|---|---|---|
| 要素の表示 | ease-out | 勢いよく現れ、落ち着きます |
| 要素の非表示 | ease-in | 退出に向けて勢いをつけます |
| 画面上の変化 | ease-in-out | 状態が滑らかに変化します |
| 注意を引く | bounce/spring | 楽しく、目立ちます |
| 背景/控えめな演出 | ease-out | 目立ちません |
アニメーションのパターン
マイクロインタラクション
BUTTON STATES:
┌─────────────────────────────────────────┐
│ Rest → Hover: scale(1.02), 100ms │
│ Hover → Active: scale(0.98), 50ms │
│ Active → Rest: scale(1), 150ms ease-out │
└─────────────────────────────────────────┘
TOGGLE SWITCH:
┌─────────────────────────────────────────┐
│ Thumb: translateX, 200ms ease-out │
│ Track: background-color, 200ms │
│ State: slight bounce at end │
└─────────────────────────────────────────┘
CHECKBOX:
┌─────────────────────────────────────────┐
│ Check mark: stroke-dashoffset animation │
│ Background: scale from center, 150ms │
│ Ripple: expanding circle, 300ms │
└─────────────────────────────────────────┘
ローディング状態
SKELETON SCREENS:
┌──────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░ │ Shimmer effect
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░ │ Linear gradient
│ ▓▓▓▓▓▓░░░░░░░░░░░░░░░░ │ Moving left to right
└──────────────────────────┘
CSS:
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
SPINNER:
- Duration: 1-2 seconds per rotation
- Easing: linear (consistent motion)
- Style: Match brand identity
ページ遷移
CROSSFADE:
├──────────────────────────────────────────┤
│ Old page: opacity 1 → 0, 200ms │
│ New page: opacity 0 → 1, 200ms │
│ Timing: Sequential or overlapping │
└──────────────────────────────────────────┘
SLIDE:
├──────────────────────────────────────────┤
│ Direction follows navigation hierarchy │
│ Forward: Slide left (new from right) │
│ Back: Slide right (prev from left) │
│ Duration: 300-400ms │
└──────────────────────────────────────────┘
SHARED ELEMENT:
├──────────────────────────────────────────┤
│ Element morphs between states │
│ Position, size, border-radius change │
│ Creates continuity between screens │
│ Duration: 300-500ms │
└──────────────────────────────────────────┘
リストアニメーション
STAGGERED ENTRANCE:
┌─ Item 1 ────────────────┐ delay: 0ms
├─ Item 2 ────────────────┤ delay: 50ms
├─ Item 3 ────────────────┤ delay: 100ms
├─ Item 4 ────────────────┤ delay: 150ms
└─ Item 5 ────────────────┘ delay: 200ms
Max total duration: 500ms
Stagger: 30-50ms per item
Animation: translateY + opacity
REORDER:
- Use FLIP technique
- Duration: 200-300ms
- Ease: ease-out
パフォーマンス
GPUアクセラレーション対応プロパティ
FAST (Compositor only):
✓ transform: translate, scale, rotate
✓ opacity
✓ filter (with will-change)
SLOW (Triggers layout/paint):
✗ width, height
✗ margin, padding
✗ top, left, right, bottom
✗ border, border-radius
✗ font-size
✗ box-shadow (repaints)
OPTIMIZATION:
will-change: transform, opacity;
/* Use sparingly! */
パフォーマンスのガイドライン
/* Good: GPU-accelerated */
.animated-element {
transform: translateX(0);
transition: transform 300ms ease-out;
}
.animated-element.moved {
transform: translateX(100px);
}
/* Bad: Layout thrashing */
.animated-element {
left: 0;
transition: left 300ms ease-out;
}
.animated-element.moved {
left: 100px;
}
FLIPテクニック
// First: Get initial position
const first = element.getBoundingClientRect();
// Last: Apply change, get final position
element.classList.add("moved");
const last = element.getBoundingClientRect();
// Invert: Calculate delta, apply inverse transform
const deltaX = first.left - last.left;
const deltaY = first.top - last.top;
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
// Play: Remove transform with transition
requestAnimationFrame(() => {
element.style.transition = "transform 300ms ease-out";
element.style.transform = "";
});
CSSアニメーションのテクニック
キーフレームアニメーション
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.element {
animation: fadeSlideIn 300ms ease-out forwards;
}
/* With steps */
@keyframes typewriter {
from {
width: 0;
}
to {
width: 100%;
}
}
.typing-text {
animation: typewriter 2s steps(20) forwards;
}
トランジション
.button {
background: var(--primary);
transform: scale(1);
transition:
background 150ms ease-out,
transform 100ms ease-out;
}
.button:hover {
background: var(--primary-hover);
transform: scale(1.02);
}
.button:active {
transform: scale(0.98);
transition-duration: 50ms;
}
animationのショートハンド
/* animation: name duration timing-function delay
iteration-count direction fill-mode play-state */
animation: slideIn 300ms ease-out 100ms 1 normal forwards running;
/* Common patterns */
animation: spin 1s linear infinite;
animation: pulse 2s ease-in-out infinite alternate;
animation: fadeIn 300ms ease-out forwards;
JavaScriptアニメーションライブラリ
比較
| ライブラリ | 最適な用途 | バンドルサイズ |
|---|---|---|
| CSS | シンプルなトランジション | 0kb |
| Web Animations API | ネイティブで高パフォーマンス | 0kb |
| GSAP | 複雑で精密なアニメーション | ~60kb |
| Framer Motion | Reactエコシステム | ~50kb |
| anime.js | タイムライン、SVG | ~17kb |
| Motion One | モダンで軽量 | ~18kb |
| Lottie | After Effectsからの書き出し | ~50kb |
Framer Motion(React)
import { motion, AnimatePresence } from "framer-motion";
// Basic animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
Content
</motion.div>;
// Variants for complex animations
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item.id} variants={itemVariants}>
{item.name}
</motion.li>
))}
</motion.ul>;
GSAP
import { gsap } from "gsap";
// Basic tween
gsap.to(".element", {
x: 100,
opacity: 1,
duration: 0.3,
ease: "power2.out",
});
// Timeline
const tl = gsap.timeline();
tl.from(".header", { y: -100, duration: 0.5 })
.from(".content", { opacity: 0, duration: 0.3 }, "-=0.2")
.from(".button", { scale: 0.8, duration: 0.2 });
// ScrollTrigger
gsap.registerPlugin(ScrollTrigger);
gsap.to(".parallax", {
y: -100,
scrollTrigger: {
trigger: ".section",
start: "top center",
end: "bottom center",
scrub: true,
},
});
アクセシビリティ
ユーザーの設定を尊重する
/* Reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Or provide simpler alternatives */
@media (prefers-reduced-motion: reduce) {
.animated-element {
/* Replace motion with opacity */
animation: none;
transition: opacity 200ms ease;
}
}
安全なアニメーションの実践
AVOID for vestibular disorders:
✗ Parallax scrolling effects
✗ Zooming/scaling animations
✗ Spinning/rotating elements
✗ Auto-playing animations
✗ Flashing (>3 times/second)
SAFE alternatives:
✓ Opacity fades
✓ Color transitions
✓ Subtle position changes
✓ User-initiated animations
WCAGガイドライン
2.2.2 Pause, Stop, Hide:
- Auto-updating content can be paused
- No time limits on reading
2.3.1 Three Flashes:
- Nothing flashes more than 3x per second
2.3.3 Animation from Interactions:
- Motion can be disabled
- Triggered animations respect preferences
モバイルでの考慮事項
タッチフィードバック
/* iOS-style tap feedback */
.button {
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.button:active {
opacity: 0.7;
transition: opacity 50ms;
}
/* Ripple effect */
.ripple {
position: relative;
overflow: hidden;
}
.ripple::after {
content: "";
position: absolute;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: scale(0);
animation: ripple 400ms ease-out;
}
プラットフォームごとの慣習
| プラットフォーム | アニメーションスタイル |
|---|---|
| iOS | スプリング物理、300-500ms、控えめ |
| Android | Material motion、200-300ms、強調 |
| Web | さまざま、通常は200-400ms |
デザインシステムへの統合
アニメーションのトークン
:root {
/* Durations */
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
--duration-slower: 600ms;
/* Easings */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.27, 1.55);
/* Combined */
--transition-default: var(--duration-normal) var(--ease-default);
--transition-fast: var(--duration-fast) var(--ease-out);
}
/* Usage */
.element {
transition: transform var(--transition-default);
}
ベストプラクティス
DO:
- 目的(フィードバック、ガイダンス)を持ってアニメーションを使います
- GPUアクセラレーション対応プロパティを使います
- 期間を500ms未満に保ちます
- アニメーションをブランドの個性に合わせます
- prefers-reduced-motionを尊重します
- ローエンド端末でテストします
- 一貫したタイミングシステムを使います
してはいけないこと:
- 装飾だけを目的にアニメーションを使う
- ユーザーの操作を妨げる
- 過度なバウンスやオーバーシュートを使う
- モーション酔いを引き起こす動きを作る
- 終了アニメーションを忘れる
- パフォーマンス指標を無視する
- 重要なコンテンツの表示を遅らせる
アニメーションチェックリスト
実装前
- アニメーションに目的がある
- 動作に適した時間になっている
- イージングが動きの意図に合っている
- パフォーマンスへの影響を評価している
実装
- GPU アクセラレーションに対応したプロパティを使用している
- prefers-reduced-motion を尊重している
- 対象デバイスで動作する
- レイアウトのスラッシングが発生しない
品質チェック
- 自然で応答性が高い
- ユーザーを待たせない
- デザインシステムとの一貫性がある
- すべてのユーザーが利用できる
コメント
Markdown 対応