routerconsole/jsp/js/onVisible.js

/**
 * @module onVisible
 * @description Provides utility functions to execute callbacks when DOM elements
 * become visible or hidden using IntersectionObserver and document visibility tracking.
 * @author dr|z3d
 * @license AGPLv3 or later
 */

/** @type {boolean} */
let isDocumentVisible = true;
/** @type {boolean} */
let listenerAdded = false;

/**
 * Adds a one-time visibility change listener to the document.
 * @function addListener
 * @returns {void}
 */
function addListener() {
  if (!listenerAdded) {
    document.addEventListener("visibilitychange", () => {
      isDocumentVisible = !document.hidden;
    });
    listenerAdded = true;
  }
}

/**
 * Executes the callback once when the element becomes visible in the viewport
 * and the document is visible.
 * @function onVisible
 * @param {Element} element - The DOM element to observe
 * @param {Function} callback - The function to call when visible, receives the element
 * @returns {void}
 */
function onVisible(element, callback) {
  if (!element || !(element instanceof Element)) { return; }
  addListener();
  new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.intersectionRatio > 0 && isDocumentVisible) {
        callback(isDocumentVisible ? element : null);
        observer.disconnect();
      }
    });
  }).observe(element);
}

/**
 * Executes the callback once when the element becomes hidden (leaves viewport)
 * while the document is visible.
 * @function onHidden
 * @param {Element} element - The DOM element to observe
 * @param {Function} callback - The function to call when hidden, receives the element
 * @returns {void}
 */
function onHidden(element, callback) {
  if (!element || !(element instanceof Element)) { return; }
  addListener();
  const observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.intersectionRatio === 0 && isDocumentVisible) {
        callback(isDocumentVisible ? element : null);
        observer.disconnect();
      }
    });
  });
  observer.observe(element);
}

export {onVisible, onHidden};