bs_lib-8.x-1.0-alpha3/js/anchor-scroll.js

js/anchor-scroll.js
/**
 * @file
 *
 * Custom scrolling to anchor target functionality.
 */

(function (drupalSettings, once) {

  'use strict';

  // Do not continue if browser do not support window.scrollY - IE family.
  if (!window.hasOwnProperty('scrollY')) {
    return;
  }

  window.BSLib = window.BSLib ? window.BSLib : {};

  /**
   * Calculate fixed elements offset.
   *
   * @return {integer}
   *   Offset of all fixed elements taking into consideration overlapping.
   */
  window.BSLib.getFixedElementsOffset = function () {
    var options = drupalSettings.bs_lib.anchor_scroll;

    // We will calculate offset distance by taking all vertical points of fixed
    // elements, sorting them and then summing offset of first point with
    // distances of all points - this will also take into consideration any
    // possible overlapping of fixed elements.

    // Get all vertical points of fixed elements.
    var points = [];
    for (var i = 0; i < options.fixed_elements.length; ++i) {
      // Lines can be empty which we need to skip.
      if (options.fixed_elements[i].trim().length !== 0) {
        var fixedElement = document.querySelector(options.fixed_elements[i]);
        if (fixedElement) {
          var boundingRect = fixedElement.getBoundingClientRect();
          points.push(boundingRect.top, boundingRect.bottom);
        }
      }
    }

    // Final offset sum, we start by adding custom offset.
    var fixedElementsOffset = options.offset;

    // Add elements offsets.
    if (points.length > 0) {
      // Sort points from smallest to biggest.
      points.sort(function (a, b) { return a - b; });

      // Add offset of the most top element point.
      fixedElementsOffset += points[0];

      // Add distances between the rest of the points.
      for (i = 1; i < points.length; ++i) {
        fixedElementsOffset += points[i] - points[i - 1];
      }
    }

    return fixedElementsOffset;
  };

  /**
   * Process click on a link anchor element.
   *
   * @param event
   * @param element
   */
  window.BSLib.processClick = function(event, element) {
    var targetElement = document.getElementById(element.href.split('#')[1]);
    if (targetElement) {
      // Prevent further processing to disable browser jumping to anchor.
      event.preventDefault();

      // Scroll to anchor.
      scrollToElement(targetElement);

      // Because we disabled browser jumping to anchor we need to manually
      // update browser location with a new hash.
      // We use replace instead of push because two phase scroll jumping is
      // messing with history scrollRestoration - it will return only to the
      // first fast scroll target.
      // @todo using of pushState probably makes more sense, can we fix
      // scrollRestoration problem somehow? Maybe we could use popstate event
      // https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event.
      history.replaceState(null, null, element.href);
    }
  };

  /**
   * Dispatch event helper.
   *
   * @param {string} name
   *   Event name.
   */
  var dispatchEvent = function (name) {
    const event = document.createEvent('Event');
    event.initEvent('bslib.anchorscroll.' + name, true, true);
    window.dispatchEvent(event);
  }

  /**
   * Calculate element offset.
   *
   * @param element
   * @return {number}
   */
  var calculateScrollOffset = function (element) {
    return element.getBoundingClientRect().top - window.BSLib.getFixedElementsOffset();
  };

  /**
   * Focus element while preventing browser scrolling to that element.
   *
   * @param element
   */
  var focusElement = function (element) {
    // Focus the target element, important for accessibility.
    element.focus({preventScroll: true});
    if (document.activeElement !== element) {
      // If element is not focusable set tabindex first so we can focus it.
      element.setAttribute('tabindex','-1');
      element.focus({preventScroll: true});
    }
  }

  /**
   * Called when first scroll part is finished.
   *
   * @param element
   */
  var firstScrollEnded = function (element) {
    dispatchEvent('firstscrollended');

    var timer;
    window.addEventListener('scroll', function(e) {
      window.requestAnimationFrame(function () {
        timer && clearTimeout(timer);
        timer = setTimeout(secondScrollEnded.bind(null, element), 50);
      });
    }, {once: true});
    window.scrollTo({
      top: window.scrollY + calculateScrollOffset(element),
      behavior: 'smooth'
    });
  }

  /**
   * Called when second scroll part is ended.
   *
   * @param element
   */
  var secondScrollEnded = function (element) {
    dispatchEvent('secondscrollended');
    focusElement(element);
  }

  /**
   * Scroll to element.
   *
   * @param element
   *   Element object to scroll.
   */
  function scrollToElement(element) {
    // We are doing scrolling in two phases first and second. The reason for
    // this is because fixed sticky elements can change it dimension and
    // position while scrolling. This is the reason we will do 2/3 of the
    // needed scrolling in first phase, recalculate scroll positions, and then
    // do the rest of the scrolling in second phase.
    var offset = calculateScrollOffset(element);
    // Only scroll if offset is big enough.
    if (Math.abs(offset) > 1) {
      // First scroll to 2/3 of the element fast.
      // Wait a bit for any elements to change.
      // Scroll last 1/3 to the element smooth.
      var timer;
      window.addEventListener('scroll', function(e) {
        window.requestAnimationFrame(function () {
          timer && clearTimeout(timer);
          timer = setTimeout(firstScrollEnded.bind(null, element), 10);
        });
      }, {once: true});
      // Scroll to element.
      window.scrollTo({
        top: window.scrollY + 2/3*offset,
      });
    }
  }

  /**
   * Process in page anchor links and attach smooth scrolling to them.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches behaviors for in page anchor links.
   */
  Drupal.behaviors.BSLibAnchorScroll = {
    attach: function (context) {
      var excludeLinks = '';
      for (var i = 0; i < drupalSettings.bs_lib.anchor_scroll.exclude_links.length; ++i) {
        excludeLinks += ':not(' + drupalSettings.bs_lib.anchor_scroll.exclude_links[i] + ')';
      }

      // Apply to every link with a hash. Originally only for href^="#" and
      // href^="/#" but that does not apply to link not on the frontpage, and
      // on multilingual websites. There is a check for empty # links in
      // processClick.
      once('bs-lib-anchor-smooth-scroll', 'a[href*="#"]' + excludeLinks, context).forEach((element) => {
        element.addEventListener('click', (e) => {
          BSLib.processClick(e, element);
        });
      });
    }
  };

  // If browser location has a hash and element with that id or name exist on
  // content automatically scroll to that element.
  if (location.hash) {
    // Query for hash element when DOM is loaded.
    window.addEventListener('DOMContentLoaded', function() {
      var hash = location.hash;

      var targetElement = document.getElementById(hash.slice(1));
      // Element which offsetParent is null is not displayed or it is fixed - we
      // do not scroll to that element.
      // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
      if (targetElement && targetElement.offsetParent != null) {
        // Reset hash to prevent native browser jumping to hash element.
        location.hash = '';

        // Trigger scroll on window load event - we need to wait native browser
        // jumping and do custom scroll after browser jump and that will happen
        // on load event, and not on DomContentLoaded.
        window.addEventListener('load', function () {
          // Restore hash part in URL without browser scrolling.
          history.replaceState(null, null, location.href + hash.slice(1));
          // Scroll to element.
          scrollToElement(targetElement);
        });
      }
    });
  }

}(drupalSettings, once));

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc