domtranslator
Version:
Simple and powerful DOM translator.
101 lines (99 loc) • 3.9 kB
JavaScript
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