Skip to content
60fps/ui

IntersectionObserver

getIntersectionObserver(options) returns an IntersectionObserver keyed by its options — every target that asks for the same root, rootMargin and threshold shares one underlying observer, so a page full of lazy images or reveal triggers stays cheap. It’s the primitive that powers InView; reach for it directly when you want raw intersection data.

Outside
0%
scroll down ↓
observed target
↑ scroll back up
Outside
0%
scroll down ↓
observed target
↑ scroll back up

Scroll inside the panel — the target is observed against the panel as its root, so the ratio bar tracks intersectionRatio live.

Usage

import { useEffect, useRef, useState } from 'react';
import { getIntersectionObserver } from '@60fps/utils';

function Tracked() {
	const ref = useRef<HTMLDivElement>(null);
	const [visible, setVisible] = useState(false);

	useEffect(() => {
		const el = ref.current;
		if (!el) return;

		const io = getIntersectionObserver({ threshold: 0.5 });
		const onIntersect = (entry: IntersectionObserverEntry) => setVisible(entry.isIntersecting);

		io.subscribe(el, onIntersect);
		return () => io.unsubscribe(el, onIntersect);
	}, []);

	return <div ref={ref}>{visible ? 'visible' : 'hidden'}</div>;
}
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { getIntersectionObserver } from '@60fps/utils';

const el = ref<HTMLElement | null>(null);
const visible = ref(false);
const io = getIntersectionObserver({ threshold: 0.5 });
const onIntersect = (entry: IntersectionObserverEntry) => (visible.value = entry.isIntersecting);

onMounted(() => el.value && io.subscribe(el.value, onIntersect));
onUnmounted(() => el.value && io.unsubscribe(el.value, onIntersect));
</script>

<template>
	<div ref="el">{{ visible ? 'visible' : 'hidden' }}</div>
</template>

API

getIntersectionObserver(options?, polyfill?)

Option Type Default Description
root Element | Document | null null The viewport ancestor to test against. `null` is the browser viewport.
rootMargin string '0px' Margin grown/shrunk around the root before computing intersections.
threshold number | number[] 0 Ratio(s) of visibility at which the callback fires.

The returned manager mirrors the ResizeObserver one:

Member Type Default Description
subscribe(el, cb) (Element, cb) => void Observe an element; the callback receives its IntersectionObserverEntry.
unsubscribe(el, cb) (Element, cb) => void Remove a callback; the element is unobserved once its last callback is gone.
observer IntersectionObserver The underlying shared observer instance.