UNPKG

hd-intersection-observer

Version:

An abstract layer that would make using intersection observer easier

172 lines (167 loc) 6.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /** * This code was copied from * https://github.com/thebuilder/react-intersection-observer/blob/main/src/observe.ts */ var observerMap = /*#__PURE__*/new Map(); var RootIds = /*#__PURE__*/new WeakMap(); var rootId = 0; var unsupportedValue = undefined; /** * What should be the default behavior if the IntersectionObserver is unsupported? * Ideally the polyfill has been loaded, you can have the following happen: * - `undefined`: Throw an error * - `true` or `false`: Set the `inView` value to this regardless of intersection state * **/ function defaultFallbackInView(inView) { unsupportedValue = inView; } /** * Generate a unique ID for the root element * @param root */ function getRootId(root) { if (!root) return "0"; if (RootIds.has(root)) return RootIds.get(root); rootId += 1; RootIds.set(root, rootId.toString()); return RootIds.get(root); } /** * Convert the options to a string Id, based on the values. * Ensures we can reuse the same observer when observing elements with the same options. * @param options */ function optionsToId(options) { return Object.keys(options).sort().filter(function (key) { return options[key] !== undefined; }).map(function (key) { return key + "_" + (key === "root" ? getRootId(options.root) : options[key]); }).toString(); } function createObserver(options) { // Create a unique ID for this observer instance, based on the root, root margin and threshold. var id = optionsToId(options); var instance = observerMap.get(id); if (!instance) { // Create a map of elements this observer is going to observe. Each element has a list of callbacks that should be triggered, once it comes into view. var elements = new Map(); var thresholds; var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { var _elements$get; // While it would be nice if you could just look at isIntersecting to determine if the component is inside the viewport, browsers can't agree on how to use it. // -Firefox ignores `threshold` when considering `isIntersecting`, so it will never be false again if `threshold` is > 0 var inView = entry.isIntersecting && thresholds.some(function (threshold) { return entry.intersectionRatio >= threshold; }); // @ts-ignore support IntersectionObserver v2 if (options.trackVisibility && typeof entry.isVisible === "undefined") { // The browser doesn't support Intersection Observer v2, falling back to v1 behavior. // @ts-ignore entry.isVisible = inView; } (_elements$get = elements.get(entry.target)) == null || _elements$get.forEach(function (callback) { callback(inView, entry, entries); }); }); }, options); // Ensure we have a valid thresholds array. If not, use the threshold from the options thresholds = observer.thresholds || (Array.isArray(options.threshold) ? options.threshold : [options.threshold || 0]); instance = { id: id, observer: observer, elements: elements }; observerMap.set(id, instance); } return instance; } /** * @param elementOrElements - DOM Element or Elements to observe * @param callback - Callback function to trigger when intersection status changes * @param options - Intersection Observer options * @param fallbackInView - Fallback inView value. * * @example * will start observing the element if its on the view port * ` * const observer = observeFunc( document.body, (isInView, entry) => { // do something }, // document or any HTML element of choice { root: document } ); // When called it will unobserve the element (for cleanup). observer(); * ` * @return Function - Cleanup function that should be triggered to unregister the observer */ function observe(elementOrElements, callback, options, fallbackInView) { if (options === void 0) { options = { rootMargin: "0px", threshold: 0.5 }; } if (fallbackInView === void 0) { fallbackInView = unsupportedValue; } var allElements = Array.isArray(elementOrElements) ? elementOrElements : [elementOrElements]; if (typeof window.IntersectionObserver === "undefined" && fallbackInView !== undefined) { allElements.forEach(function (element) { var bounds = element.getBoundingClientRect(); callback(fallbackInView, { isIntersecting: fallbackInView, target: element, intersectionRatio: typeof options.threshold === "number" ? options.threshold : 0, time: 0, boundingClientRect: bounds, intersectionRect: bounds, rootBounds: bounds }); }); return function () { // Nothing to cleanup }; } // An observer with the same options can be reused, so lets use this fact var _createObserver = createObserver(options), id = _createObserver.id, observer = _createObserver.observer, elements = _createObserver.elements; allElements.forEach(function (element) { // Register the callback listener for this element var callbacks = elements.get(element) || []; if (!elements.has(element)) { elements.set(element, callbacks); } callbacks.push(callback); observer.observe(element); }); return function unobserve() { allElements.forEach(function (element) { var callbacks = elements.get(element) || []; // Remove the callback from the callback list callbacks.splice(callbacks.indexOf(callback), 1); if (callbacks.length === 0) { // No more callback exists for element, so destroy it elements["delete"](element); observer.unobserve(element); } if (elements.size === 0) { // No more elements are being observer by this instance, so destroy it observer.disconnect(); observerMap["delete"](id); } }); }; } exports.default = observe; exports.defaultFallbackInView = defaultFallbackInView; exports.optionsToId = optionsToId; //# sourceMappingURL=hd-intersection-observer.cjs.development.js.map