Experiment · 2026

Motion Without a Library

Archived
  • CSS
  • Performance

Rebuilt every animation on this site with IntersectionObserver and CSS. Notes on what that costs you, and what it does not.

01 — WhyWhat made this worth building

The previous version of this site shipped a full animation runtime to fade sections in on scroll. I wanted to know exactly what I lost by deleting it.

02 — NotesHow it works

Scroll reveals, hover states, staggered lists and page-load entrances are all CSS transitions on a data attribute. One observer flips the attribute when an element crosses the viewport, then stops observing it. That is the entire system.

  • Kept: reveals, staggers, hovers, loading states, reduced-motion handling. All of it, at no runtime cost.
  • Lost: shared-element transitions between routes, spring physics, gesture-driven drag, interruptible animations that retarget mid-flight.
  • Verdict: if you are doing the first list, the dependency is not paying rent. If you need the second list, write it by hand once and you will want the library back.
The stagger is a CSS custom property set from the index. No JavaScript timeline, no orchestration, no library.
tsx
{items.map((item, i) => (
  <li key={item.id} data-reveal style={{ '--reveal-delay': `${i * 60}ms` } as React.CSSProperties}>
    {item.label}
  </li>
))}