pageweave/resources

Animation & Motion

Build UI animations with correct easing, duration, and choreography. Spring physics, scroll-triggered reveals, and reduced-motion safety.

· .md

Build purposeful UI animation — motion that communicates state, guides attention, and makes interfaces feel alive without feeling slow.

The Decision Framework

Before writing any animation code, answer these in order:

  1. Should this animate? Not everything should. If the animation doesn't serve feedback, orientation, focus, or continuity — skip it.
  2. What is the purpose? Feedback (button press), orientation (page transition), focus (draw eye to change), continuity (element morphs between states).
  3. What easing? Custom curves only. CSS ease is too weak. Use cubic-bezier(0.4, 0, 0.2, 1) for exits, cubic-bezier(0, 0, 0.2, 1) for entrances. Never ease-in for UI — it starts slow, feels sluggish.
  4. How fast? Under 300ms for micro-interactions. 150ms for hovers. 200–250ms for dropdowns and modals. 300–500ms for page transitions. Faster feels more responsive even when load time is identical.

Spring Physics

Springs feel more natural than duration-based animations because they simulate real physics. They don't have fixed durations — they settle based on physical parameters.

Use Motion (Framer Motion) for React: useSpring interpolates value changes with spring-like behavior instead of updating immediately.

What to Animate

Safe properties (GPU-composited, no layout thrash):

Never animate unless truly necessary:

Enter Animations

Use @starting-style for enter states:

.dialog {
  opacity: 0;
  transform: scale(0.95) translateY(8px);
  transition: opacity 200ms, transform 200ms cubic-bezier(0, 0, 0.2, 1);
}
@starting-style {
  .dialog { opacity: 0; transform: scale(0.95) translateY(8px); }
}
.dialog[open] {
  opacity: 1;
  transform: scale(1) translateY(0);
}

Never animate from scale(0) — it creates a flash. Start from scale(0.95) or scale(0.98).

Make popovers origin-aware: a popover opening from a button should transform from that button's position, not from center screen.

Stagger Animations

Staggering items within one list is legitimate. The tell is the uniform reflex — one identical entrance applied to every section. Each reveal should fit what it reveals. Vary timing, distance, and direction based on content role.

Reveal animations must enhance an already-visible default. Don't gate content visibility on a class-triggered transition — transitions pause on hidden tabs and headless renderers, so the reveal never fires and the section ships blank.

Scroll-Triggered Motion

Premium motion materials are not just transform/opacity. Blur, backdrop-filter, clip-path, mask, and shadow/glow are part of the palette when they materially improve the effect and stay smooth.

For scroll-linked effects:

Performance Rules

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

Component-Specific Patterns

Anti-Patterns

Verification

  1. Toggle prefers-reduced-motion in OS settings — all animations degrade gracefully.
  2. Tab through the interface — focus transitions should be visible but not distracting.
  3. Rapidly click/trigger the animation multiple times — it should interrupt cleanly, not queue.
  4. Profile in DevTools: no layout thrashing (purple bars in Performance tab).
  5. Test on a slow device: animations should still hit 60fps or degrade to simpler alternatives.