# Animation & Microinteractions

Guidelines for when and how to animate UI elements effectively.


## Table of Contents
1. [The Purpose of Animation](#the-purpose-of-animation)
2. [Timing & Duration](#timing-duration)
3. [Easing Functions](#easing-functions)
4. [Common Animation Patterns](#common-animation-patterns)
5. [Loading States](#loading-states)
6. [Microinteractions](#microinteractions)
7. [Accessibility Considerations](#accessibility-considerations)
8. [Performance Guidelines](#performance-guidelines)
9. [Animation Checklist](#animation-checklist)

---

## The Purpose of Animation

Animation should serve a function, not just look nice.

### Valid Reasons to Animate

| Purpose | Example |
|---------|---------|
| **Feedback** | Button press confirmation, form submission success |
| **Orientation** | Showing where something came from or went to |
| **Focus** | Drawing attention to important changes |
| **Teaching** | Demonstrating how something works |
| **Continuity** | Maintaining context during state changes |
| **Delight** | Occasional surprise (use sparingly) |

### Invalid Reasons to Animate

- "It looks cool"
- "The competition does it"
- "To show off our skills"
- Every state change
- To hide slow performance

---

## Timing & Duration

### Duration Guidelines

| Animation Type | Duration | Rationale |
|----------------|----------|-----------|
| Micro-feedback (hover, press) | 100-150ms | Must feel instant |
| Simple transitions (fade, slide) | 150-250ms | Noticeable but quick |
| Complex transitions (modal, navigation) | 250-350ms | Need time to follow |
| Entrances/Reveals | 200-400ms | Can be slightly longer |
| Decorative/Emphasis | 300-500ms | Purpose is to be noticed |

### The 200ms Rule

Most UI animations should be around 200ms:
- Faster than 100ms → Too fast to perceive
- Slower than 400ms → Feels sluggish, interrupts flow

**Exception:** Loading and progress indicators can be slower because they represent real waiting.

### Duration by Distance

Longer travel distance = longer duration (but not proportionally).

```
Small movement (8-16px):   100-150ms
Medium movement (50-100px): 150-250ms
Large movement (full screen): 250-350ms
```

---

## Easing Functions

Easing makes motion feel natural. Linear motion looks robotic.

### Common Easing Curves

| Easing | Use For | Feel |
|--------|---------|------|
| **ease-out** | Elements entering | Fast start, gentle stop |
| **ease-in** | Elements leaving | Gentle start, fast exit |
| **ease-in-out** | Elements moving within view | Smooth throughout |
| **linear** | Progress indicators, opacity changes | Mechanical (intentional) |

### When to Use Each

**Ease-out (default for entrances):**
```css
transition: transform 200ms ease-out;
```
- Modals appearing
- Notifications sliding in
- Dropdowns opening
- Tooltips appearing

**Ease-in (for exits):**
```css
transition: opacity 150ms ease-in;
```
- Modals dismissing
- Elements fading out
- Notifications leaving

**Ease-in-out (for on-screen movement):**
```css
transition: transform 250ms ease-in-out;
```
- Tab indicators sliding
- Carousel transitions
- Drawer/sidebar toggling

### Custom Cubic Bezier

For more personality, customize curves:

```css
/* Snappy entrance */
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);

/* Smooth overshoot */
transition: transform 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
```

---

## Common Animation Patterns

### Button States

**Hover:**
```css
.btn {
  transition: background-color 100ms ease-out, transform 100ms ease-out;
}
.btn:hover {
  background-color: var(--btn-hover);
}
```

**Active/Pressed:**
```css
.btn:active {
  transform: scale(0.97);
}
```

**Loading state:**
```css
.btn.loading {
  opacity: 0.7;
  pointer-events: none;
}
.btn.loading .spinner {
  animation: spin 1s linear infinite;
}
```

### Modal Entrance/Exit

**Enter:**
```css
.modal {
  opacity: 0;
  transform: scale(0.95) translateY(-10px);
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}
.modal.open {
  opacity: 1;
  transform: scale(1) translateY(0);
}
```

**Exit:**
```css
.modal.closing {
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 150ms ease-in, transform 150ms ease-in;
}
```

### Dropdown/Menu

```css
.dropdown {
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity 150ms ease-out, transform 150ms ease-out;
}
.dropdown.open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
```

### Toast/Notification

```css
.toast {
  transform: translateX(100%);
  transition: transform 300ms ease-out;
}
.toast.visible {
  transform: translateX(0);
}
.toast.exiting {
  transform: translateX(100%);
  transition: transform 200ms ease-in;
}
```

### Skeleton Loading

```css
.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
```

---

## Loading States

### Types of Loading Indicators

| Type | Use When | Example |
|------|----------|---------|
| **Spinner** | Unknown duration, short wait expected | Button submission |
| **Progress bar** | Known progress, longer operations | File upload |
| **Skeleton** | Loading content layout | Feed items |
| **Pulse/Shimmer** | Refreshing existing content | Pull to refresh |

### Spinner Guidelines

- Don't show immediately (wait 300-500ms)
- If wait < 1 second, spinner may not be needed
- Position in context (where content will appear)
- Provide cancel option for long operations

```jsx
// Delay spinner to avoid flash for fast operations
const [showSpinner, setShowSpinner] = useState(false);

useEffect(() => {
  if (isLoading) {
    const timer = setTimeout(() => setShowSpinner(true), 400);
    return () => clearTimeout(timer);
  }
  setShowSpinner(false);
}, [isLoading]);
```

### Progress Bar Guidelines

- Show percentage when meaningful
- Don't let it jump backwards
- Consider indeterminate state if progress unknown
- Complete to 100% before hiding

### Skeleton Screen Guidelines

- Match layout of actual content
- Use consistent bone shapes
- Animate subtly (shimmer, not bounce)
- Replace with content immediately when loaded

---

## Microinteractions

Small animations that provide feedback and delight.

### Effective Microinteractions

**Toggle switches:**
```css
.toggle-thumb {
  transition: transform 150ms ease-out;
}
.toggle.on .toggle-thumb {
  transform: translateX(20px);
}
```

**Checkbox check:**
```css
.checkmark {
  stroke-dasharray: 20;
  stroke-dashoffset: 20;
  transition: stroke-dashoffset 200ms ease-out;
}
.checkbox.checked .checkmark {
  stroke-dashoffset: 0;
}
```

**Like/Heart animation:**
```css
.heart {
  transform: scale(1);
  transition: transform 150ms ease-out;
}
.heart.liked {
  animation: pop 300ms ease-out;
}
@keyframes pop {
  0% { transform: scale(1); }
  50% { transform: scale(1.2); }
  100% { transform: scale(1); }
}
```

**Input focus:**
```css
.input {
  border-color: #ccc;
  transition: border-color 150ms ease-out, box-shadow 150ms ease-out;
}
.input:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
```

### When Microinteractions Help

- Confirming user action occurred
- Showing state change clearly
- Making interface feel responsive
- Guiding attention to changes

### When to Skip

- Repetitive actions (every keystroke)
- Performance-critical paths
- Accessibility mode (respect reduce-motion)

---

## Accessibility Considerations

### Respect User Preferences

```css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
```

### Provide Alternatives

- Don't rely on animation alone to convey information
- Ensure state changes are visible without animation
- Allow users to disable animations in app settings

### Avoid Problematic Animations

| Avoid | Reason |
|-------|--------|
| Flashing/strobing | Can trigger seizures |
| Parallax scrolling | Causes motion sickness |
| Auto-playing video | Distracting, accessibility |
| Infinite loops | Drains attention, battery |

---

## Performance Guidelines

### GPU-Accelerated Properties

Animate these for smooth 60fps:
- `transform` (translate, scale, rotate)
- `opacity`

Avoid animating (causes reflow/repaint):
- `width`, `height`
- `top`, `left`, `right`, `bottom`
- `margin`, `padding`
- `border-width`
- `font-size`

### Use will-change Sparingly

```css
/* Only for elements about to animate */
.modal {
  will-change: transform, opacity;
}

/* Remove after animation */
.modal.static {
  will-change: auto;
}
```

### Batch Animations

Start animations together, not staggered excessively:
- 0-50ms stagger: feels cohesive
- 100ms+ stagger: feels slow, sequential

### Test on Low-end Devices

What's smooth on your MacBook may stutter on a budget Android phone. Test on real devices or throttle CPU in DevTools.

---

## Animation Checklist

Before shipping an animation:

- [ ] Does it serve a purpose (not just decoration)?
- [ ] Is duration appropriate for the action?
- [ ] Does easing feel natural?
- [ ] Does it work with `prefers-reduced-motion`?
- [ ] Is it GPU-accelerated (transform/opacity)?
- [ ] Does it perform well on low-end devices?
- [ ] Can the interface function without it?
