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.

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.