v1/web/modules/contrib/blazy/js/base/io/bio.js

362 lines
9.8 KiB
JavaScript

/**
* @file
* Provides Intersection Observer API loader.
*
* This file is not loaded when `No JavaScript` enabled, unless exceptions met.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
* @see https://developers.google.com/web/updates/2016/04/intersectionobserver
* @see https://www.npmjs.com/package/intersection-observer
* @see https://github.com/w3c/IntersectionObserver
* @see https://caniuse.com/?search=visualViewport
* @todo https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API
* @todo remove traces of fallback to be taken care of by old bLazy fork.
*/
/* global define, module */
(function (root, factory) {
'use strict';
var ns = 'Bio';
var db = root.dBlazy;
// Inspired by https://github.com/addyosmani/memoize.js/blob/master/memoize.js
if (db.isAmd) {
// AMD. Register as an anonymous module.
define([ns, db, root], factory);
}
else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but only CommonJS-like
// environments that support module.exports, like Node.
module.exports = factory(ns, db, root);
}
else {
// Browser globals (root is window).
root[ns] = factory(ns, db, root);
}
}((this || module || {}), function (ns, $, _win) {
'use strict';
if ($.isAmd) {
_win = window;
}
/**
* Private variables.
*/
var _doc = document;
var _root = _doc;
var _winData = {};
var _bioTick = 0;
var _ww = 0;
var _revTick = 0;
var _counted = 0;
var _opts = {};
var _bgClass = 'b-bg';
var _isVisible = 'is-b-visible';
var _media = 'media';
var _parent = '.' + _media;
var _addClass = 'addClass';
var _removeClass = 'removeClass';
var _initialized = false;
var _resizing = false;
var _validateDelay = 25;
// Cache our prototype.
var fn = Bio.prototype;
fn.constructor = Bio;
/**
* Constructor for Bio, Blazy IntersectionObserver.
*
* @param {object} options
* The Bio options.
*
* @return {Bio}
* The Bio instance.
*
* @namespace
*/
function Bio(options) {
var me = $.extend({}, fn, this);
me.name = ns;
me.options = _opts = $.extend({}, $._defaults, options || {});
_bgClass = _opts.bgClass || _bgClass;
_validateDelay = _opts.validateDelay || _validateDelay;
_parent = _opts.parent || _parent;
_root = _opts.root || _root;
// DOM ready fix. Ain't a culprit.
setTimeout(function () {
me.reinit();
});
return me;
}
// Prepare prototype to interchange with Blazy as fallback.
fn.count = 0;
fn.erCount = 0;
fn.resizeTick = 0;
fn.destroyed = false;
fn.options = {};
fn.lazyLoad = function (el, winData) {};
fn.loadImage = function (el, isBg, winData) {};
fn.resizing = function (el, winData) {};
fn.prepare = function () {};
fn.windowData = function () {
return $.isUnd(_winData.vp) ? $.windowData(this.options, true) : _winData;
};
// BC for interchanging with bLazy.
// @todo merge with bLazy::load.
fn.load = function (elms, revalidate, opts) {
var me = this;
elms = elms && $.toArray(elms);
// @todo remove once infinite pager regression fixed properly like before.
if (!$.isUnd(opts)) {
me.options = $.extend({}, me.options, opts || {});
}
// Manually load elements regardless of being disconnected, or not, relevant
// for Slick slidesToShow > 1 which rebuilds clones of unloaded elements.
$.each(elms, function (el) {
if (me.isValid(el) || ($.isElm(el) && revalidate)) {
intersecting.call(me, el, revalidate);
}
});
};
fn.isLoaded = function (el) {
return $.hasClass(el, this.options.successClass);
};
fn.isValid = function (el) {
return $.isElm(el) && !this.isLoaded(el);
};
fn.revalidate = function (force) {
var me = this;
// Prevents from too many revalidations unless needed.
if ((force === true || me.count !== _counted) && (_revTick < _counted)) {
var elms = me.elms = $.findAll(_root, $.selector(me.options));
if (elms.length) {
me.observe(true);
_revTick++;
}
}
};
fn.destroyQuietly = function (force) {
var me = this;
var opts = me.options;
// Infinite pager like IO wants to keep monitoring infinite contents.
// Multi-breakpoint BG/ ratio may want to update during resizing.
if (!me.destroyed && (force || $.isUnd(Drupal.io))) {
var el = $.find(_doc, $.selector(opts, ':not(.' + opts.successClass + ')'));
if (!$.isElm(el)) {
me.destroy(force);
}
}
};
fn.destroy = function (force) {
var me = this;
var opts = me.options;
var io = me.ioObserver;
// Do not disconnect if any error found.
if (me.destroyed || (me.erCounted > 0 && !force)) {
return;
}
// Disconnect when all entries are loaded, if so configured.
var done = (_bioTick === me.count - 1) && opts.disconnect;
if (done || force) {
if (io) {
io.disconnect();
}
$.unload(me);
me.count = 0;
me.elms = [];
me.ioObserver = null;
me.destroyed = true;
}
};
fn.observe = function (reobserve) {
var me = this;
var elms = me.elms;
// Only initialize the observer if destroyed, and IO.
if ($.isIo && (me.destroyed || reobserve)) {
_winData = $.initObserver(me, interact, elms, true);
me.destroyed = false;
}
// Observe as IO, or initialize old bLazy as fallback.
if (!_initialized || reobserve) {
$.observe(me, elms, true);
_initialized = true;
}
};
fn.reinit = function () {
var me = this;
me.destroyed = true;
init(me);
};
function intersecting(el, revalidate) {
var me = this;
var opts = me.options;
var count = me.count;
var io = me.ioObserver;
if (_bioTick === count - 1) {
me.destroyQuietly();
}
// Unlike ResizeObserver/ infinite pager, IntersectionObserver is done.
if (io && me.isLoaded(el) && !el.bloaded && opts.isMedia && !revalidate) {
io.unobserve(el);
el.bloaded = true;
_bioTick++;
}
// Image may take time to load after being hit, and it may be intersected
// several times till marked loaded. Ensures it is hit once regardless
// of being loaded, or not. No real issue with normal images on the page,
// until having VIS alike which may spit out new images on AJAX request.
if (!el.bhit || revalidate) {
// Makes sure to have media loaded beforehand.
me.lazyLoad(el, _winData);
_counted++;
// Marks it hit/ requested. Not necessarily loaded.
el.bhit = true;
revalidate = false;
}
// If not extending/ overriding, at least provide the option.
// Currenty IO.module wants to keep watching for infinite pager since it
// only has a single button/ link to observe so should not be locked.
// @todo move it back right after ::lazyLoad once IO.module smarter.
if ($.isFun(opts.intersecting)) {
opts.intersecting(el, opts);
}
// If not extending/ overriding, also allows to listen to.
$.trigger(el, 'bio.intersecting', {
options: opts
});
}
// This function is called by two observers: IO and RO.
function interact(entries) {
var me = this;
var opts = me.options;
var vp = $.vp;
var ww = $.ww;
var entry = entries[0];
var isBlur = $.isBlur(entry);
var isResizing = $.isResized(me, entry);
var visibleClass = opts.visibleClass;
// RO is another abserver.
if (isResizing) {
_winData = $.updateViewport(opts);
$.onresizing(me, _winData);
}
else {
// Stop IO watching if destroyed, unless a visibleClass is defined:
// Animation, BG color on being visible, infinite pager, or lazyloaded
// blocks. Infinite pager is a valid sample since it has a single link
// to observe for infinite click events. Unobserve should be left to them.
if (me.destroyed && !visibleClass) {
return;
}
}
// Load each on entering viewport.
$.each(entries, function (e) {
var target = e.target;
var el = target || e;
var resized = $.isResized(me, e);
var visible = $.isVisible(e, vp);
var cn = $.closest(el, _parent) || el;
var loaded = me.isLoaded(el);
// To make efficient blur filter via CSS, etc. Blur filter is expensive.
$[visible && !loaded ? _addClass : _removeClass](cn, _isVisible);
// For different toggle purposes regardless being loaded, or not.
// Avoid using the reserved `is-b-visible`, use `is-b-inview`, etc.
if (visibleClass && $.isStr(visibleClass)) {
$[visible ? _addClass : _removeClass](cn, visibleClass);
}
// The element is being intersected.
if (visible) {
intersecting.call(me, el);
}
// The element is being resized.
_resizing = resized && _ww > 0;
if (_resizing && !isBlur) {
// Ensures only before settled, or if any different from previous size.
if (_ww !== ww) {
me.resizing(el, _winData);
}
me.resizeTick++;
}
// Provides option such as to animate bg or elements regardless position.
// See gridstack.parallax.js.
if ($.isFun(opts.observing)) {
opts.observing(e, visible, opts);
}
});
_ww = ww;
}
// Initializes the IO with fallback to old bLazy.
function init(me) {
// Swap data-[SRC|SRCSET] for non-js version once, if not choosing Native.
// Native lazy markup is triggered by enabling `No JavaScript` lazy option.
me.prepare();
var elms = me.elms = $.findAll(_root, $.selector(me.options));
me.count = elms.length;
me._raf = [];
me._queue = [];
// Observe elements. Old blazy as fallback is also initialized here.
// IO will unobserve, or disconnect. Old bLazy will self destroy.
me.observe(true);
}
return Bio;
}));