observeVisibility
Observes visibility changes of a DOM element using IntersectionObserver.
1/**
2 * Observes visibility changes of a DOM element using IntersectionObserver.
3 *
4 * @param element - The element to observe.
5 * @param callback - A function called with a boolean indicating visibility.
6 * @param options - Optional IntersectionObserver options.
7 * @returns A cleanup function to stop observing.
8 */
9export function observeVisibility(
10 element: Element,
11 callback: (isVisible: boolean) => void,
12 options?: IntersectionObserverInit
13): () => void {
14 const observer = new IntersectionObserver(([entry]) => {
15 callback(entry.isIntersecting);
16 }, options);
17
18 observer.observe(element);
19
20 return () => observer.disconnect();
21}
Efficient Visibility Detection
Uses the native
IntersectionObserver
API, which is optimized by the browser and avoids performance issues from manual polling or scroll listeners.Declarative Cleanup
Returns a cleanup function, making it easy to manage lifecycle and avoid memory leaks in dynamic UI environments.
Flexible Configuration
Supports all standard
IntersectionObserverInit
options likethreshold
,root
, androotMargin
for fine-tuned visibility control.Lightweight and Focused
Provides a minimal, composable utility that focuses only on visibility tracking without bundling unrelated logic.
Tests | Examples
1let mockObserve: jest.Mock;
2let mockDisconnect: jest.Mock;
3let mockIntersectionObserver: jest.Mock;
4
5beforeAll(() => {
6 mockObserve = jest.fn();
7 mockDisconnect = jest.fn();
8
9 mockIntersectionObserver = jest.fn((callback, _options) => {
10 (mockIntersectionObserver as any)._callback = callback;
11 return {
12 observe: mockObserve,
13 disconnect: mockDisconnect
14 };
15 });
16
17 Object.defineProperty(window, 'IntersectionObserver', {
18 writable: true,
19 configurable: true,
20 value: mockIntersectionObserver
21 });
22});
23
24beforeEach(() => {
25 jest.clearAllMocks();
26});
27
28test('calls observe on the target element', () => {
29 const element = document.createElement('div');
30 const callback = jest.fn();
31
32 observeVisibility(element, callback);
33
34 expect(mockObserve).toHaveBeenCalledWith(element);
35});
36
37test('invokes callback with isIntersecting true', () => {
38 const element = document.createElement('div');
39 const callback = jest.fn();
40
41 observeVisibility(element, callback);
42
43 const entry = { isIntersecting: true };
44 (mockIntersectionObserver as any)._callback([entry]);
45
46 expect(callback).toHaveBeenCalledWith(true);
47});
48
49test('invokes callback with isIntersecting false', () => {
50 const element = document.createElement('div');
51 const callback = jest.fn();
52
53 observeVisibility(element, callback);
54
55 const entry = { isIntersecting: false };
56 (mockIntersectionObserver as any)._callback([entry]);
57
58 expect(callback).toHaveBeenCalledWith(false);
59});
60
61test('returns a cleanup function that disconnects observer', () => {
62 const element = document.createElement('div');
63 const callback = jest.fn();
64
65 const cleanup = observeVisibility(element, callback);
66 cleanup();
67
68 expect(mockDisconnect).toHaveBeenCalled();
69});
Common Use Cases
Lazy Loading Content
Load images, components, or data only when they enter the viewport to improve performance.
Triggering Animations
Start animations or transitions when elements become visible on screen.
Tracking Viewport Metrics
Monitor if promotional banners, ads, or CTAs are seen by the user.
Infinite Scroll Implementation
Detect when a "load more" element enters view to trigger additional data fetching.
Sticky Header or UI Adjustments
Observe sentinel elements to toggle UI behaviors like fixing headers or showing floating actions.