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>