UNPKG

domtranslator

Version:
101 lines (99 loc) 3.9 kB
import { isIntersectableNode } from './isIntersectableNode'; import { isAttributeNode, isElementNode } from './nodes'; /** * Returns the node owner element: * - For Element returns itself * - For Attr returns owner ownerElement * - For Text and other node returns parentElement */ export function getElementOfNode(node) { if (isElementNode(node)) { return node; } if (isAttributeNode(node)) { return node.ownerElement; } return node.parentElement; } /** * Observes DOM nodes for intersection with the viewport and triggers callbacks when they become visible. * Class supports observing both elements and nodes (Text, Attr, etc.) */ export class NodesIntersectionObserver { intersectionObserver; // Stores nodes and their owner element that are being observed for intersection elementNodesMap = new WeakMap(); nodeCallbacksMap = new WeakMap(); constructor(intersectionConfig) { this.intersectionObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry) => { const node = entry.target; // Skip nodes that are not under observation or still is not intersected if (!this.elementNodesMap.has(node) || !entry.isIntersecting) return; this.triggerNestedNodes(node); // Process the element once and stop observing it this.elementNodesMap.delete(node); observer.unobserve(node); }); }, intersectionConfig); } /** * Starts observing the node for intersection. * When the owner element of the node intersects the viewport, the callback is invoked. * Then the owner element and all its tracked nodes are automatically removed from observation. * * (Owner element means: element itself for Element, parent element for Text, owner element for Attr) */ observe(node, callback) { const targetElement = getElementOfNode(node); // Immediately invoke the callback if the node has no owner or is not intersectable if (!targetElement || !isIntersectableNode(targetElement)) { callback(node); return; } this.nodeCallbacksMap.set(node, callback); const observedNodes = this.elementNodesMap.get(targetElement); if (observedNodes) { observedNodes.add(node); } else { this.elementNodesMap.set(targetElement, new Set([node])); this.intersectionObserver.observe(targetElement); } } /** * Stops observing the node and removes it from observation */ unobserve(node) { const targetElement = getElementOfNode(node); if (!targetElement) return; const observedNodes = this.elementNodesMap.get(targetElement); if (!observedNodes || !observedNodes.has(node)) return; // remove only the specified node observedNodes.delete(node); this.nodeCallbacksMap.delete(node); // if no more nodes are tracked under this ownerElement, stop observing it if (observedNodes.size === 0) { this.elementNodesMap.delete(targetElement); this.intersectionObserver.unobserve(targetElement); } } /** * Calls callbacks for all nodes associated with the specified element and removes their callbacks from storage */ triggerNestedNodes(node) { const ownedNodes = this.elementNodesMap.get(node); if (!ownedNodes) return; ownedNodes.forEach((node) => { const callback = this.nodeCallbacksMap.get(node); this.nodeCallbacksMap.delete(node); if (callback) callback(node); }); } } //# sourceMappingURL=../sourcemaps/utils/NodesIntersectionObserver.js.map