forked from a64f7bb4-7358-4778-9fbe-3b882c34cc1d/v1
362 lines
9.8 KiB
JavaScript
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;
|
|
|
|
}));
|