grid-scroller.js

// =============================================================================
/*! countinmultiples */
/*! author: Toni Price (https://toni.rbind.io) */
// =============================================================================

import { Level, ctxt, log } from './logging.js';

// =============================================================================

/**
 * Class to handle scrolling of the grid if the animation goes vertically
 *   outside of the current viewport.
 * @typedef {Object} GridScroller
 */
export class GridScroller {

  #observerOpts = null;

  #observer = null;

  /**
   * Constructor for GridScroller class.
   */
  constructor() {
    this.#observerOpts = {
      // Set root to null to make it the viewport
      root: null,
      rootMargin: '0px',
      // Set to 1 so that the callback will fire when even 1px is outside of the
      // viewport
      threshold: 1,
    };
  }

  /**
   * Resets the window scroll position to the top left.
   */
  resetScrollPos = () => {
    window.scrollTo({
      top: 0,
      left: 0,
      behavior: 'smooth',
    });
  };

  /**
   * Handles scrolling when an element goes vertically outside of the current
   *   viewport.
   *
   * @param {string} targetId - HTML id of the target element to observe.
   * @param {number} vScroll - An integer value for the number of pixels to
   *   to scroll along the Y axis when the currently animated cell goes past
   *   the end of the vertical viewport. Default: 100.
   */
  initialise = (targetId, vScroll) => {

    // Vertical scroll distance in pixels
    vScroll = vScroll || 220;

    log(Level.Vbose, `-> Initialising GridScroller for '${targetId}'`, ctxt());

    // IntersectionObserver: find out when element is outside viewport
    // asked May 12, 2019 at 13:51
    // Helenesh
    // https://stackoverflow.com/questions/56099931/intersectionobserver-find-out-when-element-is-outside-viewport
    // [31jan24]

    /**
     * Checks if any of the given HTML elements are outside the viewport.
     *
     * @param {Array<Object>} entries An array of HTML elements to check.
     * @param {Object} observer The observer object for which to check the
     *   entries.
     */
    function onAnimate(entries) {
      entries.forEach((entry) => {

        const isInView = entry.isIntersecting;

        if (!isInView) {
          log(Level.Vbose, `'${entry.target.id}' is not in view`, ctxt());
          window.scrollBy({
            top: vScroll,
            left: 0,
            behavior: 'smooth',
          });
        }
      });
    }

    this.#observer = new IntersectionObserver(onAnimate, this.#observerOpts);
  };

  /**
   * Sets the observer to observe a particular target element.
   *
   * @param {string} targetId - HTML id of the target element to observe.
   */
  startObserving = (targetId) => {
    if (this.#observer != null) {
      const target = document.querySelector(`#${targetId}`);
      if (target != null) {
        log(Level.Vbose, `-> Starting to observe ${targetId}`, ctxt());
        this.#observer.observe(target);
      }
    }
  };

  /**
   * Stops the observer from observing a particular target element.
   * Note that no exception is thrown if the specified element is not being
   * observed so it is safe to call this function multiple times.
   * (See Mozilla MDN Web APIs: Intersection Observer API at
   * https://developer.mozilla.org/en-US/docs/Web/API)
   *
   * @param {Object} targetElmt - The target HTML element to unobserve.
   */
  stopObservingElmt = (targetElmt) => {
    if (this.#observer != null) {
      if (targetElmt != null) {
        this.#observer.unobserve(targetElmt);
      }
    }
  };

  /**
   * Stops the observer from observing the element with the given target id.
   * Note that no exception is thrown if the specified element is not being
   * observed so it is safe to call this function multiple times.
   * (See Mozilla MDN Web APIs: Intersection Observer API at
   * https://developer.mozilla.org/en-US/docs/Web/API)
   *
   * @param {string} targetId - HTML id of the target element to unobserve.
   */
  stopObserving = (targetId) => {
    if (this.#observer != null) {
      const target = document.querySelector(`#${targetId}`);
      if (target != null) {
        this.#observer.unobserve(target);
      }
    }
  };
}

// =============================================================================