useIsVisible Deep Dive
The useIsVisible hook provides a reactive way to detect if an element is currently within the viewport (or a specified root) using the Intersection Observer API.
Core Concepts
useIsVisible wraps the native IntersectionObserver to provide a React-friendly interface. It returns a setRef function (or a ref object) that you must attach to the element you want to observe.
const { setRef, isVisible } = useIsVisible({
rootMargin: '50px',
multiple: true
});
return (
<div ref={setRef}>
{isVisible ? 'I am visible!' : 'I am hidden...'}
</div>
);
Observer Management
To optimize performance, Archibald uses an internal observerMap. This ensures that multiple components using the same intersection options (like rootMargin or threshold) share a single IntersectionObserver instance instead of creating redundant ones.
- Deduplication: Options are stringified and used as keys in the map.
- Reference Counting: The system tracks how many elements are currently using a specific observer and disconnects the observer only when the last element is unobserved.
Common Options
multiple
false(Default): The observer is disconnected as soon as the element becomes visible for the first time. This is ideal for "reveal" animations or lazy-loading.true: The hook continues to track visibility, updatingisVisibleevery time the element enters or leaves the viewport.
rootMargin
Defines a set of offsets that effectively grow or shrink the area used for intersections. Default is '100px 0px 0px 0px'.
delay
Introduces a delay (in milliseconds) before updating the isVisible state. This can be used to prevent rapid flickering if an element is on the edge of the viewport.
How it Works Step-by-Step
- Mounting: The hook initializes with a default
isVisiblevalue. - Ref Assignment: When
setRefis attached to a DOM node, theregisterElementfunction is triggered. - Observer Retrieval: The hook looks for an existing
IntersectionObserverin theobserverMapthat matches the provided options. If none exists, a new one is created. - Observation: The DOM node is passed to the observer's
.observe()method. - Intersection Event: When the browser detects an intersection change:
- The
handleIntersectcallback is fired. - If
isIntersectingistrueandmultipleisfalse, the element is immediately unobserved. - A React
startTransitionis used to update theshowstate, ensuring the UI remains responsive.
- The
- Unmounting: When the component unmounts (or the target element changes), the
unregisterElementfunction is called, which decrements the reference count and potentially cleans up the global observer.
Best Practice: Use the multiple: false strategy for performance-intensive tasks like lazy-loading images or complex components to minimize the overhead of continuous intersection tracking.
Full Example
Basic Observer
import { useIsVisible } from '@archibald/client';
function TestComponent() {
const { isVisible, ref, setRef } = useIsVisible();
useEffect(() => {
if (isVisible) {
console.log('Element is now visible:', ref.current);
}
}, [isVisible]);
return <div ref={setRef}>Observe me!</div>;
}
Observing an External Element
import { useIsVisible } from '@archibald/client';
import { useSSR } from '@archibald/core';
function TestComponent() {
const { isBrowser } = useSSR();
const element = isBrowser ? document.getElementById('external-target') : null;
const { isVisible, ref } = useIsVisible({ element });
return (
<div>
<div id="external-target">Target Element</div>
<p>Is Target Visible? {isVisible ? 'Yes' : 'No'}</p>
</div>
);
}