forked from a64f7bb4-7358-4778-9fbe-3b882c34cc1d/v1
290 lines
8.0 KiB
JavaScript
290 lines
8.0 KiB
JavaScript
/**
|
|
* @file
|
|
* Provides Intersection Observer API loader for media.
|
|
*
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
|
* @see https://developers.google.com/web/updates/2016/04/intersectionobserver
|
|
*/
|
|
|
|
/* global define, module */
|
|
(function (root, factory) {
|
|
|
|
'use strict';
|
|
|
|
var ns = 'BioMedia';
|
|
var db = root.dBlazy;
|
|
var bio = root.Bio;
|
|
|
|
// Inspired by https://github.com/addyosmani/memoize.js/blob/master/memoize.js
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define([ns, db, bio], 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, bio);
|
|
}
|
|
else {
|
|
// Browser globals (root is window).
|
|
root[ns] = factory(ns, db, bio);
|
|
}
|
|
})(this, function (ns, $, _bio) {
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* Private variables.
|
|
*/
|
|
var _doc = document;
|
|
var _data = 'data-';
|
|
var _src = 'src';
|
|
var _srcSet = 'srcset';
|
|
var _dataSrc = _data + _src;
|
|
var _dataSrcset = _data + _srcSet;
|
|
var _imgSources = [_srcSet, _src];
|
|
var _erCounted = 0;
|
|
var _isDeferChecked = false;
|
|
|
|
// Inherits Bio prototype.
|
|
var _super = Bio.prototype;
|
|
var fn = BioMedia.prototype = Object.create(_super);
|
|
fn.constructor = BioMedia;
|
|
|
|
/**
|
|
* Constructor for BioMedia, Blazy IntersectionObserver for media.
|
|
*
|
|
* @param {object} options
|
|
* The BioMedia options.
|
|
*
|
|
* @return {object}
|
|
* The BioMedia instance.
|
|
*
|
|
* @namespace
|
|
*/
|
|
function BioMedia(options) {
|
|
var me = _bio.apply($.extend({}, _super, $.extend({}, fn, this)), arguments);
|
|
|
|
me.name = ns;
|
|
|
|
return me;
|
|
}
|
|
|
|
// Extends Bio prototype.
|
|
fn.lazyLoad = function (el, winData) {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var parent = el.parentNode;
|
|
var isBg = $.isBg(el);
|
|
var isPicture = $.equal(parent, 'picture');
|
|
var isImage = $.equal(el, 'img');
|
|
var isVideo = $.equal(el, 'video');
|
|
var isDataset = $.hasAttr(el, _dataSrc);
|
|
|
|
// Initializes blur, if any.
|
|
if ($.blur) {
|
|
$.blur(el);
|
|
}
|
|
|
|
// PICTURE elements.
|
|
if (isPicture) {
|
|
if (isDataset) {
|
|
$.mapSource(el, _srcSet, true);
|
|
|
|
// Tiny controller image inside picture element won't get preloaded.
|
|
$.mapAttr(el, _src, true);
|
|
}
|
|
|
|
_erCounted = defer(me, el, true, opts);
|
|
}
|
|
// VIDEO elements.
|
|
else if (isVideo) {
|
|
_erCounted = $.loadVideo(el, true, opts);
|
|
}
|
|
else {
|
|
// IMG or DIV/ block elements got preloaded for better UX with loading.
|
|
// Native doesn't support DIV, fix it.
|
|
if (isImage || isBg) {
|
|
me.loadImage(el, isBg, winData);
|
|
}
|
|
// IFRAME elements, etc.
|
|
else {
|
|
if ($.hasAttr(el, _src)) {
|
|
if ($.attr(el, _dataSrc)) {
|
|
$.mapAttr(el, _src, true);
|
|
}
|
|
|
|
_erCounted = defer(me, el, true, opts);
|
|
}
|
|
}
|
|
}
|
|
me.erCount = _erCounted;
|
|
};
|
|
|
|
// Compatibility between Native and old data-[SRC|SRSET] approaches.
|
|
fn.loadImage = function (el, isBg, winData) {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var img = new Image();
|
|
var isResimage = $.hasAttr(el, _srcSet);
|
|
var isDataset = $.hasAttr(el, _dataSrc);
|
|
var currSrc = isDataset ? _dataSrc : _src;
|
|
var currSrcset = isDataset ? _dataSrcset : _srcSet;
|
|
|
|
var preload = function () {
|
|
if ('decode' in img) {
|
|
img.decoding = 'async';
|
|
}
|
|
|
|
if (isBg && $.isFun($.bgUrl)) {
|
|
img.src = $.bgUrl(el, winData);
|
|
}
|
|
else {
|
|
if (isDataset) {
|
|
$.mapAttr(el, _imgSources, false);
|
|
}
|
|
|
|
img.src = $.attr(el, currSrc);
|
|
}
|
|
|
|
if (isResimage) {
|
|
img.srcset = $.attr(el, currSrcset);
|
|
}
|
|
};
|
|
|
|
var load = function (el, ok) {
|
|
if (isBg && $.isFun($.bg)) {
|
|
$.bg(el, winData);
|
|
_erCounted = $.status(el, ok, opts);
|
|
}
|
|
else {
|
|
_erCounted = defer(me, el, ok, opts);
|
|
}
|
|
};
|
|
|
|
preload();
|
|
|
|
// Preload `img` to have correct event handlers.
|
|
$.decode(img)
|
|
.then(function () {
|
|
load(el, true);
|
|
})
|
|
.catch(function () {
|
|
load(el, isResimage);
|
|
|
|
// Allows to re-observe.
|
|
if (!isResimage) {
|
|
el.bhit = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
fn.resizing = function (el, winData) {
|
|
var me = this;
|
|
var isBg = $.isBg(el, me.options);
|
|
|
|
// Fix dynamic multi-breakpoint background to avoid loaders workarounds.
|
|
if (isBg) {
|
|
me.loadImage(el, isBg, winData);
|
|
}
|
|
};
|
|
|
|
// Applies the defer loading as per https://drupal.org/node/3120696.
|
|
function defer(me, el, status, opts) {
|
|
if (!_isDeferChecked) {
|
|
var elms = natively(me, 'defer');
|
|
if (elms) {
|
|
$.each(elms, function (elm) {
|
|
$.attr(elm, 'loading', 'lazy');
|
|
});
|
|
}
|
|
_isDeferChecked = true;
|
|
}
|
|
|
|
return $.status(el, status, opts);
|
|
}
|
|
|
|
// Since bLazy, which has no supports for Native, is a fallback, it is easier
|
|
// now to work with Native. No more need to hook into load event separately,
|
|
// no deferred invocation till one loaded, no hijacking.
|
|
// No more fights under a single source of truth. It is a total swap.
|
|
// As mentioned in the doc, Native at least Chrome starts loading images
|
|
// 8000px, hardcoded, before they are entering the viewport. Meaning harsh,
|
|
// makes fancy stuffs like blur useless. And bad because blur filter
|
|
// is very expensive, and when they are triggered before visible, will block.
|
|
// @see /admin/help/blazy_ui# NATIVE LAZY LOADING
|
|
// With bIO as the main loader, the game changed, quoted from:
|
|
// https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Author_fast-loading_HTML_pages
|
|
// "Note that lazily-loaded images may not be available when the load event is
|
|
// fired. You can determine if a given image is loaded by checking to see if
|
|
// the value of its Boolean complete property is true."
|
|
// Old bLazy relies on onload, meaning too early loaded decision for Native,
|
|
// the reason for our previous deferred invocation, not decoding like what bIO
|
|
// did which is more precise as suggested by the quote.
|
|
// Assumed, untested, fine with combo IO + decoding checks before blur spits.
|
|
// Shortly we are in the right direction to cope with Native vs. data-[SRC].
|
|
// @done recheck IF wrong so to put back https://drupal.org/node/3120696.
|
|
// Almost not wrong, no blur nor `b-loaded` were added till intersected, but
|
|
// added a new `loading:defer` to solve 8000px threshold.
|
|
function natively(me, key) {
|
|
var opts = me.options;
|
|
|
|
if (!$.isNativeLazy) {
|
|
return [];
|
|
}
|
|
|
|
// ::findAll is already optimized with a single null check, no extra checks.
|
|
// The `a` keyword found in `auto, eager, lazy`, not `defer`.
|
|
key = key || 'a';
|
|
var dataset = $.selector(opts, '[data-src][loading*="' + key + '"]:not(.b-blur)');
|
|
var els = $.findAll(_doc, dataset);
|
|
|
|
// We are here if `No JavaScript` is being disabled.
|
|
if (els.length) {
|
|
// Reset attributes, and let supportive browsers lazy load natively.
|
|
$(els).mapAttr(['srcset', 'src'], true)
|
|
// Also supports PICTURE which contains SOURCEs. Excluding VIDEO.
|
|
.mapSource(false, true, false);
|
|
}
|
|
return els;
|
|
}
|
|
|
|
// https://caniuse.com/dom-manip-convenience
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith
|
|
function webp(me) {
|
|
if ($.webp.isSupported()) {
|
|
return;
|
|
}
|
|
|
|
var sel = function (prefix) {
|
|
prefix = prefix || '';
|
|
// IE9 err: :not(picture img)
|
|
return $.selector(me.options, '[' + prefix + 'srcset*=".webp"]');
|
|
};
|
|
|
|
var elms = $.findAll(_doc, sel());
|
|
if (!elms.length) {
|
|
elms = $.findAll(_doc, sel('data-'));
|
|
}
|
|
|
|
if (elms.length) {
|
|
$.webp.run(elms);
|
|
}
|
|
}
|
|
|
|
fn.prepare = function () {
|
|
var me = this;
|
|
|
|
// @todo lock it back once AJAX-loaded contents fixed.
|
|
natively(me);
|
|
|
|
// Runs after native set to minimize works.
|
|
if ($.webp) {
|
|
webp(me);
|
|
}
|
|
};
|
|
|
|
return BioMedia;
|
|
|
|
});
|