viewmotion

How it works

viewmotion is built on three pillars: a single IntersectionObserver, CSS @keyframes, and declarative data attributes.

Architecture overview

1. Initialization

When you call initMotion(), viewmotion scans the DOM for all elements with data-motion or data-stagger attributes. It creates a single global IntersectionObserver that monitors them all.

2. Observation

The observer watches each element. When an element enters the viewport (based on the configured threshold), viewmotion reads the element’s data-motion configuration and applies the corresponding CSS animation class.

3. Animation

The CSS class triggers a @keyframes animation — hardware-accelerated by the browser using transform and opacity. Once the animation completes, the element is unobserved to free resources.

Why IntersectionObserver?

Unlike scroll event listeners, IntersectionObserver is asynchronous and runs off the main thread. This means:

  • No scroll jank — observations don’t block rendering
  • No debouncing needed — the browser handles callback timing
  • One observer for all elements — scales to hundreds of elements with no performance penalty

Why CSS @keyframes?

CSS animations are composited by the GPU, keeping the main thread free. viewmotion only animates transform, opacity, and filter — properties that don’t trigger layout or paint.

Note

viewmotion does not use requestAnimationFrame or any JavaScript animation loop. Once the CSS class is applied, the browser handles everything.

Data attribute flow

The data flow is simple and predictable:

<!-- Before entering viewport: -->
<h1 data-motion='{"preset":"fade-up"}' style="opacity:0">Hello</h1>

<!-- After entering viewport: -->
<h1
  data-motion='{"preset":"fade-up"}'
  class="vm-revealed"
  style="animation: vm-fade-up 600ms ease both"
>
  Hello
</h1>