ab-interchange
Version:
AB-interchange: While responsive image loading is not really an easy task even today, here is a solution to manage conditional (based on breakpoints) loading of img, background-image or even HTML content.
529 lines (420 loc) • 14.5 kB
JavaScript
// modules are defined as an array
// [ module function, map of requires ]
//
// map of requires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the require for previous bundles
parcelRequire = (function (modules, cache, entry, globalName) {
// Save the require from previous bundle to this closure if any
var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
var nodeRequire = typeof require === 'function' && require;
function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
if (!jumped && currentRequire) {
return currentRequire(name, true);
}
// If there are other bundles on this page the require from the
// previous one is saved to 'previousRequire'. Repeat this as
// many times as there are bundles until the module is found or
// we exhaust the require chain.
if (previousRequire) {
return previousRequire(name, true);
}
// Try the node require function if it exists.
if (nodeRequire && typeof name === 'string') {
return nodeRequire(name);
}
var err = new Error('Cannot find module \'' + name + '\'');
err.code = 'MODULE_NOT_FOUND';
throw err;
}
localRequire.resolve = resolve;
localRequire.cache = {};
var module = cache[name] = new newRequire.Module(name);
modules[name][0].call(module.exports, localRequire, module, module.exports, this);
}
return cache[name].exports;
function localRequire(x){
return newRequire(localRequire.resolve(x));
}
function resolve(x){
return modules[name][1][x] || x;
}
}
function Module(moduleName) {
this.id = moduleName;
this.bundle = newRequire;
this.exports = {};
}
newRequire.isParcelRequire = true;
newRequire.Module = Module;
newRequire.modules = modules;
newRequire.cache = cache;
newRequire.parent = previousRequire;
newRequire.register = function (id, exports) {
modules[id] = [function (require, module) {
module.exports = exports;
}, {}];
};
var error;
for (var i = 0; i < entry.length; i++) {
try {
newRequire(entry[i]);
} catch (e) {
// Save first error but execute all entries
if (!error) {
error = e;
}
}
}
if (entry.length) {
// Expose entry point to Node, AMD or browser globals
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
var mainExports = newRequire(entry[entry.length - 1]);
// CommonJS
if (typeof exports === "object" && typeof module !== "undefined") {
module.exports = mainExports;
// RequireJS
} else if (typeof define === "function" && define.amd) {
define(function () {
return mainExports;
});
// <script>
} else if (globalName) {
this[globalName] = mainExports;
}
}
// Override the current require with this new one
parcelRequire = newRequire;
if (error) {
// throw error from earlier, _after updating parcelRequire_
throw error;
}
return newRequire;
})({"cnAZ":[function(require,module,exports) {
// polyfill customEvent pour IE
;(function() {
if (typeof window.CustomEvent === "function") return false;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
// throttle events with requestAnimationFrame
(function() {
var throttle = function(type, name) {
var running = false;
var func = function() {
if (running) return;
running = true;
window.requestAnimationFrame(function() {
window.dispatchEvent(new CustomEvent(name));
running = false;
});
};
window.addEventListener(type, func);
};
/* init - you can init any event */
throttle("resize", "ab-resize");
throttle("scroll", "ab-scroll");
throttle("mousemove", "ab-mousemove");
throttle("touchmove", "ab-touchmove");
})();
window.AB = {
// deep extend function
extend: function() {
var extended = {};
var deep = false;
var i = 0;
var length = arguments.length;
if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]'){
deep = arguments[0];
i++;
}
var merge = function(obj) {
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
extended[prop] = window.AB.extend(true, extended[prop], obj[prop]);
} else {
extended[prop] = obj[prop];
}
}
}
};
for (; i < length; i++) {
merge(arguments[i]);
}
return extended;
},
// test if a string is a JSON
isJson: function(str) {
try {
JSON.parse(str);
} catch(e) {
return false;
}
return true;
},
runUpdaters: function(plugin) {
if (window.AB.options[plugin]) {
window.AB.plugins[plugin](window.AB.options[plugin]);
} else {
for(var options in AB.options){
if(window.AB.options.hasOwnProperty(options)) {
window.AB.plugins[options](window.AB.options[options]);
}
}
}
},
plugins: {},
options: {},
};
module.exports = window.AB;
},{}],"TYbL":[function(require,module,exports) {
'use strict';
window.AB = require('another-brick');
var mediaQuery = function(opt) {
window.AB.mediaQuery = (function() {
var _settings = opt || {bp: {}};
function _getCurrent() {
var sizes = [];
for (var key in _settings.bp) {
if (_settings.bp.hasOwnProperty(key) && window.matchMedia(_settings.bp[key]).matches) {
sizes.push(key);
}
}
return sizes;
}
function _updateSizes() {
var newSize = _getCurrent();
// check if it's updated
if (newSize.join('|') !== _currentStore.join('|')) {
_currentStore = newSize;
window.dispatchEvent(new CustomEvent('changed.ab-mediaquery'));
}
}
function is(size) {
return _settings.bp[size] ? window.matchMedia(_settings.bp[size]).matches : false;
}
// get current breakpoints
var _currentStore = _getCurrent()
// change on resize
window.addEventListener('ab-resize', _updateSizes);
return {
get current() { return _currentStore; },
is: is
};
})();
};
window.AB.plugins.mediaQuery = mediaQuery;
module.exports = window.AB;
},{"another-brick":"cnAZ"}],"Focm":[function(require,module,exports) {
'use strict';
window.AB = require('ab-mediaquery');
var pluginName = 'abInterchange';
var attr = 'data-ab-interchange';
var attrSrc = attr + '-src';
var defaultSettings = {
mode: 'lazy-img',
lazySettings: {
offscreen: 1.25,
layout: 'fluid' // or 'fixed' for fixed dimensions
}
}; // Run right methods depending on 'mode'
function _replace() {
if (this._replaced) return;
switch (this.mode) {
case 'lazy-img':
_replaceImg.call(this, true);
break;
case 'background':
_replaceBackground.call(this);
break;
case 'lazy-background':
_replaceBackground.call(this, true);
break;
}
} // Replace source: lazy-img
function _replaceImg(lazy) {
if (this._imgNode.src === this.currentPath || lazy && !this.inView()) return;
this._imgNode.src = this.currentPath;
this._imgNode.addEventListener('load', _isReplaced.bind(this)); // we are done
this._replaced = true;
} // Replace source: background or lazy-background
function _replaceBackground(lazy) {
if (this.el.style.backgroundImage === 'url("' + this.currentPath + '")' || lazy && !this.inView()) return;
this.el.style.backgroundImage = this.currentPath ? 'url(' + this.currentPath + ')' : 'none';
this.el.addEventListener('load', _isReplaced.bind(this));
} // build the DOM for lazy-img mode
function _setPlaceholder() {
var placeholderNode = document.createElement('div');
var imgNode = document.createElement('img');
var alt = this.el.getAttribute('alt');
var widthHeight = _getWidthHeight.call(this);
var width = widthHeight.width;
var height = widthHeight.height;
var isNotReady = !width || !height;
var fragment = document.createDocumentFragment();
if (this.mode !== 'lazy-img' || isNotReady) return;
this.el.innerHTML = '';
this.el.style.overflow = 'hidden';
this.el.style.position = 'relative';
this.el.classList.add('ab-interchange-loading');
if (this.settings.lazySettings.layout === 'fixed') {
this.el.style.height = height + 'px';
this.el.style.width = width + 'px';
}
placeholderNode.classList.add('ab-interchange-placeholder');
placeholderNode.style.paddingTop = (height / width * 100).toFixed(2) + "%";
imgNode.style.position = 'absolute';
imgNode.style.top = imgNode.style.right = imgNode.style.bottom = imgNode.style.left = 0;
imgNode.style.maxHeight = imgNode.style.minHeight = imgNode.style.maxWidth = imgNode.style.minWidth = '100%';
imgNode.style.height = 0;
imgNode.alt = alt === null ? '' : alt; // always put an 'alt'
fragment.appendChild(placeholderNode);
fragment.appendChild(imgNode);
this.el.appendChild(fragment);
this._imgNode = this.el.querySelector('img');
} // run after source is replaced
function _isReplaced() {
this.el.classList.remove('ab-interchange-loading');
var event = new CustomEvent(pluginName + '.replaced', {
detail: {
element: this.el
}
});
window.dispatchEvent(event);
}
function _events() {
var observer;
var rootMargin = ""; // update path, then replace
window.addEventListener('changed.ab-mediaquery', this.resetDisplay.bind(this));
if (this.mode === 'lazy-img' || this.mode === 'lazy-background') {
if ('IntersectionObserver' in window) {
rootMargin = parseInt((this.settings.lazySettings.offscreen - 1) * window.innerHeight) + 'px';
observer = new IntersectionObserver(_onScroll.bind(this), {
root: null,
rootMargin: '0px 0px ' + rootMargin + ' 0px',
threshold: 0
});
observer.observe(this.el);
} else {
// 'ab-scroll' is a custom event from AB dependency
window.addEventListener('ab-scroll', _onScroll.bind(this));
}
}
} // build rules from attribute
function _generateRules() {
// retro compatibility: sources inside 'attr'
var getAttrSrc = this.el.getAttribute(attrSrc) || this.el.getAttribute(attr);
var rulesList = [];
var rules = getAttrSrc.match(/\[[^\]]+\]/g);
var rule, path, query;
for (var i = 0, len = rules.length; i < len; i++) {
rule = rules[i].slice(1, -1).split(', ');
path = rule.slice(0, -1).join('');
query = rule[rule.length - 1];
rulesList.push({
path: path,
query: query
});
}
this.rules = rulesList;
} // Change source path depending on rules
function _updatePath() {
var path = '';
var rules = this.rules; // Iterate through each rule
for (var i = 0, len = rules.length; i < len; i++) {
if (window.AB.mediaQuery.is(rules[i].query)) {
path = rules[i].path;
}
} // if path hasn't changed, return
if (this.currentPath === path) return;
this.currentPath = path;
_replace.call(this);
}
function _onScroll() {
// when inView, no need to use 'delayed'
if (this.inView() && !this._replaced) {
_replace.call(this);
}
} // get width and height from attributes and manage multiple dimensions
function _getWidthHeight() {
var width = this.el.getAttribute('width');
var height = this.el.getAttribute('height');
var widthObj = {};
var heightObj = {};
if (window.AB.isJson(width) && window.AB.isJson(height)) {
widthObj = JSON.parse(width);
heightObj = JSON.parse(height);
for (var key in widthObj) {
if (widthObj.hasOwnProperty(key) && window.AB.mediaQuery.is(key)) {
width = widthObj[key];
height = heightObj[key];
}
}
}
return {
width: width,
height: height
};
} // init instance
function _init() {
_setPlaceholder.call(this);
_events.call(this);
_generateRules.call(this);
_updatePath.call(this);
}
var Plugin = function Plugin(el, options) {
this.el = el;
var dataOptions = window.AB.isJson(this.el.getAttribute(attr)) ? JSON.parse(this.el.getAttribute(attr)) : {};
this.settings = window.AB.extend(true, defaultSettings, options, dataOptions);
this.rules = [];
this.currentPath = '';
this.mode = this.settings.mode;
this.replaced = false;
this._imgNode = this.el; // where the source will be updated
// no need when using 'img' on browsers supporting that, except when using lazy loading
if ((this.el.tagName === 'IMG' || this.el.parentNode.tagName === 'PICTURE' || this.el.getAttribute('srcset')) && window.HTMLPictureElement) return;
_init.call(this);
};
Plugin.prototype = {
// Force element refresh
resetDisplay: function resetDisplay() {
this._replaced = false;
_setPlaceholder.call(this);
_updatePath.call(this);
},
inView: function inView() {
var windowHeight = window.innerHeight;
var rect = this.el.getBoundingClientRect();
var elHeight = this.el.offsetHeight;
var checkTop = -elHeight - windowHeight * (this.settings.lazySettings.offscreen - 1);
var checkBottom = windowHeight + windowHeight * (this.settings.lazySettings.offscreen - 1);
return rect.top >= checkTop && rect.top <= checkBottom;
}
};
function interchange(options) {
var elements = document.querySelectorAll('[' + attr + ']');
for (var i = 0, len = elements.length; i < len; i++) {
if (elements[i][pluginName]) continue;
elements[i][pluginName] = new Plugin(elements[i], options);
} // register plugin and options
if (!window.AB.options[pluginName]) {
window.AB.options[pluginName] = options;
}
}
window.AB.plugins.interchange = interchange;
module.exports = window.AB;
},{"ab-mediaquery":"TYbL"}]},{},["Focm"], null)