Framework-agnostic engines for scroll, element visibility, and viewport-relative position — with adapters for Vue 3, Nuxt, and React, sharing one rAF loop and a pooled IntersectionObserver.

Reveal-on-scroll effects, parallax, a reading-progress indicator — a common enough need that usually gets solved either with a heavy animation library or hand-rolled scroll listeners and IntersectionObservers duplicated in every component. Inview handles that mechanics once, at the shared-engine level.
That classic sense-of-depth trick: a background image or illustration drifts slower (or faster) than the actual content while the page scrolls. Normally that means hand-computing an element's scroll position every frame and turning it into a CSS transform from scratch. The ready-made version already clamps the offset at the edges, takes a speed and an axis, and switches itself off for anyone who's asked their system for less motion.
Fade-in, a slide up from below, a gentle scale-in — every one of those effects comes down to the same thing: knowing the moment an element enters view, and firing the animation once, right then. Cards that share the same tracking options share one IntersectionObserver instead of spawning one each — the effect stays cheap even across a feed of hundreds of them.
A thin bar that fills edge-to-edge as the page scrolls — a common fixture on blogs and long-form pieces. Normally that means dividing the distance scrolled by the document's height by hand and redoing the math on every scroll frame. The ready-made progress value is already a plain 0-to-1 number, updated on its own on the same shared animation loop as the rest of the scroll state.
Navigation stays out of the way while reading, then reappears the moment you scroll back up — a familiar pattern on mobile sites and long landing pages. Normally that means comparing the current scroll position against the last one by hand, on every scroll event. The scroll direction is already computed on the same shared loop as the rest of the scroll state, and resets on its own once scrolling pauses.

Position, direction, progress, and velocity for the window or an element — all derived on one shared animation-frame loop instead of a separate loop per subscription. The "currently scrolling" state resets on its own after a pause in scrolling.

Reactively tells you whether an element is visible, which way it entered or left, and can fire just once. Elements that share the same tracking options share one observer instead of spawning a new one for each — real savings on pages with a lot of them.

An element's bounding box, how far it's traveled through the viewport, and its distance from the screen's center — a ready-made data set for reveal effects and progress indicators, no hand-rolled geometry per component.

Turns an element's position in the viewport into a CSS transform with a configurable speed, axis, edge clamping, and easing curve — and switches itself off for anyone who's asked their system for less motion.

Every function is available with no explicit import anywhere in a Nuxt app. Default options are set once in the project config and picked up automatically — nothing extra to pass at each call site.

The same API and the same behavior as the Vue version, just without reactive refs — plain values that update on their own, plus a safe snapshot for server-side rendering.
createScrollEngine() batches scroll events into one shared requestAnimationFrame loop and hands back progress, direction, and velocity — one subscribe() call away.
import { createScrollEngine } from '@macrulez/inview-core'
const progressBar = document.querySelector('#progress-bar') as HTMLElement
const scroll = createScrollEngine()
scroll.subscribe((state) => {
progressBar.style.transform = `scaleX(${state.progress})`
})createElementTracker() tracks how far an element has travelled through the viewport; bindCSSVar() writes that straight onto a CSS custom property, so the actual fade/translate lives in plain CSS.
import { createElementTracker, bindCSSVar } from '@macrulez/inview-core'
const card = document.querySelector('#card') as HTMLElement
const tracker = createElementTracker(card)
tracker.subscribe((state) => {
bindCSSVar(card, 'reveal', state.viewportProgress)
})