Skip to content

Visibility Engine ​

createVisibilityEngine(pool?) — wraps a pooled IntersectionObserver with enter/leave edge detection and once support, without any framework-specific reactivity.

ts
function createVisibilityEngine(pool?: ObserverPool): VisibilityEngine

pool defaults to the shared observerPool singleton (see below) — pass your own new ObserverPool() to isolate observers from the rest of the app, which is mainly useful in tests.

Return value ​

observe(element, options, callback) ​

(element: Element, options: VisibilityObserveOptions, callback: (info: IntersectionInfo) => void) => () => void

Starts observing element. Returns an unsubscribe function.

destroy() ​

() => void

Runs every registered unsubscribe function and clears them.

VisibilityObserveOptions ​

FieldTypeDefault
rootElement | Document | nullnull
rootMarginstring'0px'
thresholdnumber | number[]0
oncebooleanundefined — when true, observe() unobserves the element automatically after the first isIntersecting: true.
onEnter(info: IntersectionInfo) => void— called when info.edge is 'enter-top' or 'enter-bottom'.
onLeave(info: IntersectionInfo) => void— called when info.edge is 'leave-top' or 'leave-bottom'.

IntersectionInfo ​

Passed to callback (and to onEnter/onLeave) on every change:

FieldTypeNotes
isIntersectingboolean
intersectionRationumber
boundingClientRectDOMRectLike
edgeIntersectionEdge | undefined'enter-top' | 'enter-bottom' | 'leave-top' | 'leave-bottom' — only set when isIntersecting differs from the previous call.

Example:

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

const visibility = createVisibilityEngine()

const unobserve = visibility.observe(
  document.querySelector('#hero')!,
  { threshold: 0.5, once: true },
  (info) => {
    if (info.isIntersecting) console.log('half visible')
  },
)

Observer Pooling ​

ObserverPool / observerPool — pools native IntersectionObserver instances by (root, rootMargin, threshold), so many elements observed with the same options share one observer instead of each spawning its own. createVisibilityEngine() uses the shared observerPool singleton by default.

ts
class ObserverPool {
  observe(
    element: Element,
    options: ObserverPoolOptions,
    callback: (entry: IntersectionObserverEntry) => void,
  ): () => void
}

const observerPool: ObserverPool

ObserverPoolOptions is { root?, rootMargin?, threshold? } — the same three fields listed above, with the same defaults (null/'0px'/0), applied when a new native IntersectionObserver is actually constructed for a given key. The pool disconnects and drops a native observer once its last observed element unobserves. In an environment without IntersectionObserver at all, observe() returns a no-op unsubscribe rather than throwing.

The pool key compares a threshold array by value ([0, 0.25, 0.5].join(',')), not by reference — passing a fresh { threshold: [0, 0.5] } object literal on every single call still reuses the same native observer for elements sharing that value, no need to hoist the array to a shared constant yourself.

ObserverPool.stats() ​

() => ObserverPoolStat[]

Debug-only introspection into the pool — how many native IntersectionObserver instances are actually alive right now, and how many elements sit on each. Useful for confirming pooling is really collapsing observers as expected in a given app, instead of silently fragmenting because a fresh inline threshold array turned out to carry different values on every render than you assumed.

ts
interface ObserverPoolStat {
  key: string // the (root, rootMargin, threshold) pooling key
  elementCount: number
}
ts
import { observerPool } from '@macrulez/inview-core'

observerPool.stats()
// [{ key: 'window|0px|0,0.5', elementCount: 12 }, { key: 'r|0px|0', elementCount: 3 }]

Looking for the reactive version? See Element Visibility for Vue, or React Hooks for React — both wrap this same engine and share the same pool. Need to handle many elements — including ones that don't exist in the DOM yet — without calling a composable/hook per element? See Reveal Controller.