this repo has no description
at main 100 lines 4.0 kB view raw
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}