Skip to content

Element Viewport Tracking ​

createElementTracker(element, options?) — tracks an element's position relative to the viewport (its rect, how far it's traveled through the viewport, and its distance from the viewport's center), updated on the shared requestAnimationFrame loop. Pass root to measure against a scrollable container instead — an element scrolling inside overflow: auto (a dashboard panel, a modal) has its own "viewport" that isn't the window at all.

ts
function createElementTracker(element: HTMLElement, options?: ElementTrackerOptions): ElementTracker

interface ElementTrackerOptions {
  root?: Element | null // default null (the window)
}

Return value ​

getState() ​

() => ElementTrackerState

Reads the current state synchronously, without subscribing.

subscribe(callback) ​

(callback: (state: ElementTrackerState) => void) => () => void

Calls callback immediately with the current state, then again only when the state actually changes (a shallow field comparison — no notification on an rAF tick where nothing moved). Returns an unsubscribe function.

destroy() ​

() => void

ElementTrackerState ​

FieldTypeNotes
rectDOMRectLikeA plain { top, left, right, bottom, width, height } snapshot of getBoundingClientRect() — always window-relative, regardless of root.
viewportProgressnumber0..1 — from the element entering at the "viewport" frame's bottom edge to leaving at its top edge. That frame is the window by default, or root's own bounding rect when set.
distanceFromCenternumberPixel offset between the element's vertical center and the frame's vertical center (window or root) — negative when the element is above center.

Without a window (server-side rendering), this returns a static tracker whose state is always { rect: { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }, viewportProgress: 0, distanceFromCenter: 0 }.

Example:

ts
import { createElementTracker } from '@macrulez/inview-core'

const tracker = createElementTracker(document.querySelector('#card')!)

tracker.subscribe((state) => {
  console.log(`${Math.round(state.viewportProgress * 100)}% through the viewport`)
})

Tracking inside a scrollable panel instead of the window:

ts
const panel = document.querySelector('#dashboard-panel')!
const tracker = createElementTracker(document.querySelector('#widget')!, { root: panel })

Looking for the reactive version, or a ready-made parallax effect? See Element Viewport Position and Parallax Layer for Vue, or React Hooks for React.