Homepage (site/index.html): integration-v14 promoted, Writings section integrated with 33 pieces clustered by type (stories/essays/miscellany), Writings welcome lightbox, content frame at 98% opacity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
429 lines
8.8 KiB
Markdown
429 lines
8.8 KiB
Markdown
# Animation & Microinteractions
|
|
|
|
Guidelines for when and how to animate UI elements effectively.
|
|
|
|
## 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?
|