this repo has no description
1import { getRafQueue } from '@amp/web-app-components/src/utils/rafQueue';
2// TODO: rdar://91082022 (JMOTW: Performance - Refactor IntersectionObserver Admin Locally)
3import IntersectionObserverAdmin from 'intersection-observer-admin';
4
5// Threshold is how much of the target element is currently visible within the
6// root's intersection ratio, as a value between 0.0 and 1.0.
7// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/intersectionRatio
8//
9// Examples:
10// 0 = a single visible pixel counts as the target being "visible"
11// 1 = a single non-visible pixel counts as the target being "not visible""
12const DEFAULT_VIEWPORT_THRESHOLD = 0.6;
13
14// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver#properties
15// Adding `callback` to the type since you can only pass an array or object into actions
16type configObject = {
17 root?: Element | null;
18 rootMargin?: string;
19 threshold?: number;
20 callback?: Function;
21};
22
23let intersectionObserverAdmin;
24
25/**
26 * IntersectionObserver action to track when an element comes in to/goes out of the visible viewport.
27 * Useful for stopping animations of elements no longer visible, starting animations when
28 * they appear/reappear, applying/removing styles, etc.
29 *
30 * Callbacks will be called with a boolean depending on if the item is intersecting (true) or not (false).
31 *
32 * Utilizes Intersection Observer Admin (https://github.com/snewcomer/intersection-observer-admin) to allow
33 * the setup of a single Intersection Observer queue that handles observations in a way that allows each
34 * element to have it's own callback and IntersectionObserver configuration.
35 *
36 * @function intersectionObserver
37 * @param {Element} target Element to track (DOM element, Document, or null for top-level document viewport)
38 * @param {configObject} options callback function for handling viewport visiblity changes
39 *
40 * @example `<div use:intersectionObserver={{ callback: handleIntersectionUpdate }}></div>`
41 * @example `<div use:intersectionObserver={{
42 * callback: handleIntersectionUpdate,
43 * root: document.querySelector('some-element')
44 * }}></div>`
45 * @example `<div use:intersectionObserver={{
46 * callback: handleIntersectionUpdate,
47 * root: document.querySelector('some-element'),
48 * threshold: 1
49 * }}></div>`
50 * @example `<div use:intersectionObserver={{
51 * callback: handleIntersectionUpdate,
52 * root: document.querySelector('some-element'),
53 * rootMargin: '0px 0px 0px 0px',
54 * threshold: 1
55 * }}></div>`
56 */
57export function intersectionObserver(
58 target: Element,
59 options: configObject = {},
60): { destroy: () => void } {
61 if (!('IntersectionObserver' in window)) return;
62
63 if (!options.callback) {
64 console.warn(
65 'Use of intersectionObserver action requires passing in a callback function',
66 );
67 return;
68 }
69
70 const rafQueue = getRafQueue();
71 const customCallback = options.callback;
72
73 // Clone options to manipulate object without side effects
74 // Assign initial default threshold, overridden by any settings in `options`
75 const optionsObj = Object.assign(
76 { threshold: DEFAULT_VIEWPORT_THRESHOLD },
77 options,
78 );
79 delete optionsObj.callback;
80
81 const callback = (ioEntry) => {
82 rafQueue.add(() => customCallback(ioEntry.isIntersecting));
83 };
84
85 if (!intersectionObserverAdmin) {
86 intersectionObserverAdmin = new IntersectionObserverAdmin();
87 }
88
89 // Add callbacks that will be called when observer detects entering and leaving viewport
90 intersectionObserverAdmin.addEnterCallback(target, callback);
91 intersectionObserverAdmin.addExitCallback(target, callback);
92
93 intersectionObserverAdmin.observe(target, optionsObj);
94
95 return {
96 destroy() {
97 intersectionObserverAdmin.unobserve(target, optionsObj);
98 },
99 };
100}