InView
Framework-agnostic engines for scroll, element visibility, and viewport-relative position — with adapters for Vue 3, Nuxt, and React sharing one requestAnimationFrame loop and a pooled IntersectionObserver.
Features
- Scroll tracking — position, direction, progress, and velocity for the window or a scrollable element, all derived on one shared rAF loop instead of a separate loop per subscriber.
- Visibility tracking — reactive
IntersectionObserver-backed visibility with enter/leave edge detection and aoncemode. Observers are pooled by(root, rootMargin, threshold), so many elements with the same options share one native observer. - Element viewport position — an element's bounding rect, how far it's traveled through the viewport (
viewportProgress, 0..1), and its distance from the viewport's center — the geometry a reveal effect or progress indicator actually needs, already computed. Measurable against a scrollable container instead of the window via arootoption. - A parallax layer primitive — turns
viewportProgressinto a CSS transform with a configurable speed, axis, edge clamping, and easing, and disables itself underprefers-reduced-motionautomatically. - Scroll spy —
createScrollSpytracks which of a list of targets currently sits in a band near the viewport's center and reports its index, the standard nav-highlight idiom (a docs sidebar, a table of contents), sharing the same pooled observer as everything else. - SSR-safe everywhere — every engine and composable/hook returns a static no-op snapshot when there's no
window, and starts a real subscription once it's running on the client — no<ClientOnly>wrapping required. - Vue, Nuxt, and React adapters over one core —
useScroll,useElementVisibility,useElementViewport, anduseParallaxLayerexist as Vue composables, auto-imported Nuxt composables, and React hooks with the same API and behavior — all three built on the same framework-agnostic engines underneath. - Reveal at scale, without per-element wiring —
createRevealControllerscans the DOM for a selector (default[data-reveal], .reveal) and toggles a class/attribute as elements enter the viewport, including ones added later, picked up via aMutationObserver. Every option can be overridden per element via adata-reveal-*attribute, andobserve()/unobserve()add elements directly without relying on the selector scan. Vue and React both get auseRevealControllerlifecycle wrapper; Vue additionally gets av-revealdirective and an<InView>renderless component, React its own<InView>render-prop component,useRevealhook, and an<InviewProvider>for app-level defaults.staggerDelaycomputes a CSS delay for staggered groups, with an optional repeating "cycle" mode for long lists, and is skipped entirely underprefers-reduced-motion. - Small reactive utilities —
useScrollTo/scrollToElementsmooth-scroll a container so an element lands at its edge with an offset;usePrefersReducedMotionturns the staticprefers-reduced-motioncheck into a live value that updates on the media query'schangeevent;useStaggeris a reactive wrapper aroundstaggerDelay. - Every adapter re-exports the full core —
@macrulez/inview-vueand@macrulez/inview-reactre-export everything from@macrulez/inview-core, and@macrulez/inview-nuxtauto-imports it too, so reaching a lower-level engine likecreateRevealControllernever needs a separate@macrulez/inview-coreinstall.
How It Works
The package is four npm packages sharing one architecture: a framework-agnostic core (@macrulez/inview-core) does the actual work — scroll tracking, IntersectionObserver pooling, rAF batching — with no framework dependency at all. @macrulez/inview-vue wraps each core engine in a reactive composable (Refs in, Refs out). @macrulez/inview-react wraps the same core engines in hooks built on useSyncExternalStore, mirroring the Vue composables' API 1:1 but with plain values instead of refs. @macrulez/inview-nuxt is a thin Nuxt module on top of the Vue package: it auto-imports every composable, utility, and core engine, registers the v-reveal directive globally, and seeds SSR-safe defaults from nuxt.config.ts on the client.
Picking an adapter is the only decision — install @macrulez/inview-vue for Vue (or let @macrulez/inview-nuxt pull it in), or @macrulez/inview-react for React. The underlying engines, and the options/behavior they expose, stay the same either way.