(function (window, factory) {
	var lazySizes = factory(window, window.document);
	window.lazySizes = lazySizes;
	if (typeof module == 'object' && module.exports) {
		module.exports = lazySizes;
	}
})(window, function l(window, document) {
	'use strict';
	/*jshint eqnull:true */
	if (!document.getElementsByClassName) {
		return;
	}

	var lazysizes, lazySizesConfig;

	var docElem = document.documentElement;

	var Date = window.Date;

	var supportPicture = window.HTMLPictureElement;

	var _addEventListener = 'addEventListener';

	var _getAttribute = 'getAttribute';

	var addEventListener = window[_addEventListener];

	var setTimeout = window.setTimeout;

	var requestAnimationFrame = window.requestAnimationFrame || setTimeout;

	var requestIdleCallback = window.requestIdleCallback;

	var regPicture = /^picture$/i;

	var loadEvents = ['load', 'error', 'lazyincluded', '_lazyloaded'];

	var regClassCache = {};

	var forEach = Array.prototype.forEach;

	var hasClass = function (ele, cls) {
		if (!regClassCache[cls]) {
			regClassCache[cls] = new RegExp('(\\s|^)' + cls + '(\\s|$)');
		}
		return (
			regClassCache[cls].test(ele[_getAttribute]('class') || '') &&
			regClassCache[cls]
		);
	};

	var addClass = function (ele, cls) {
		if (!hasClass(ele, cls)) {
			ele.setAttribute(
				'class',
				(ele[_getAttribute]('class') || '').trim() + ' ' + cls
			);
		}
	};

	var removeClass = function (ele, cls) {
		var reg;
		if ((reg = hasClass(ele, cls))) {
			ele.setAttribute(
				'class',
				(ele[_getAttribute]('class') || '').replace(reg, ' ')
			);
		}
	};

	var addRemoveLoadEvents = function (dom, fn, add) {
		var action = add ? _addEventListener : 'removeEventListener';
		if (add) {
			addRemoveLoadEvents(dom, fn);
		}
		loadEvents.forEach(function (evt) {
			dom[action](evt, fn);
		});
	};

	var triggerEvent = function (elem, name, detail, noBubbles, noCancelable) {
		var event = document.createEvent('Event');

		if (!detail) {
			detail = {};
		}

		detail.instance = lazysizes;

		event.initEvent(name, !noBubbles, !noCancelable);

		event.detail = detail;

		elem.dispatchEvent(event);
		return event;
	};

	var updatePolyfill = function (el, full) {
		var polyfill;
		if (
			!supportPicture &&
			(polyfill = window.picturefill || lazySizesConfig.pf)
		) {
			if (full && full.src && !el[_getAttribute]('srcset')) {
				el.setAttribute('srcset', full.src);
			}
			polyfill({ reevaluate: true, elements: [el] });
		} else if (full && full.src) {
			el.src = full.src;
		}
	};

	var getCSS = function (elem, style) {
		return (getComputedStyle(elem, null) || {})[style];
	};

	var getWidth = function (elem, parent, width) {
		width = width || elem.offsetWidth;

		while (
			width < lazySizesConfig.minSize &&
			parent &&
			!elem._lazysizesWidth
		) {
			width = parent.offsetWidth;
			parent = parent.parentNode;
		}

		return width;
	};

	var rAF = (function () {
		var running, waiting;
		var firstFns = [];
		var secondFns = [];
		var fns = firstFns;

		var run = function () {
			var runFns = fns;

			fns = firstFns.length ? secondFns : firstFns;

			running = true;
			waiting = false;

			while (runFns.length) {
				runFns.shift()();
			}

			running = false;
		};

		var rafBatch = function (fn, queue) {
			if (running && !queue) {
				fn.apply(this, arguments);
			} else {
				fns.push(fn);

				if (!waiting) {
					waiting = true;
					(document.hidden ? setTimeout : requestAnimationFrame)(run);
				}
			}
		};

		rafBatch._lsFlush = run;

		return rafBatch;
	})();

	var rAFIt = function (fn, simple) {
		return simple
			? function () {
					rAF(fn);
			  }
			: function () {
					var that = this;
					var args = arguments;
					rAF(function () {
						fn.apply(that, args);
					});
			  };
	};

	var throttle = function (fn) {
		var running;
		var lastTime = 0;
		var gDelay = lazySizesConfig.throttleDelay;
		var rICTimeout = lazySizesConfig.ricTimeout;
		var run = function () {
			running = false;
			lastTime = Date.now();
			fn();
		};
		var idleCallback =
			requestIdleCallback && rICTimeout > 49
				? function () {
						requestIdleCallback(run, { timeout: rICTimeout });

						if (rICTimeout !== lazySizesConfig.ricTimeout) {
							rICTimeout = lazySizesConfig.ricTimeout;
						}
				  }
				: rAFIt(function () {
						setTimeout(run);
				  }, true);
		return function (isPriority) {
			var delay;

			if ((isPriority = isPriority === true)) {
				rICTimeout = 33;
			}

			if (running) {
				return;
			}

			running = true;

			delay = gDelay - (Date.now() - lastTime);

			if (delay < 0) {
				delay = 0;
			}

			if (isPriority || delay < 9) {
				idleCallback();
			} else {
				setTimeout(idleCallback, delay);
			}
		};
	};

	//based on http://modernjavascript.blogspot.de/2013/08/building-better-debounce.html
	var debounce = function (func) {
		var timeout, timestamp;
		var wait = 99;
		var run = function () {
			timeout = null;
			func();
		};
		var later = function () {
			var last = Date.now() - timestamp;

			if (last < wait) {
				setTimeout(later, wait - last);
			} else {
				(requestIdleCallback || run)(run);
			}
		};

		return function () {
			timestamp = Date.now();

			if (!timeout) {
				timeout = setTimeout(later, wait);
			}
		};
	};

	(function () {
		var prop;

		var lazySizesDefaults = {
			lazyClass: 'lazyload',
			loadedClass: 'lazyloaded',
			loadingClass: 'lazyloading',
			preloadClass: 'lazypreload',
			errorClass: 'lazyerror',
			//strictClass: 'lazystrict',
			autosizesClass: 'lazyautosizes',
			srcAttr: 'data-src',
			srcsetAttr: 'data-srcset',
			sizesAttr: 'data-sizes',
			//preloadAfterLoad: false,
			minSize: 40,
			customMedia: {},
			init: true,
			expFactor: 1.5,
			hFac: 0.8,
			loadMode: 2,
			loadHidden: true,
			ricTimeout: 0,
			throttleDelay: 125,
		};

		lazySizesConfig =
			window.lazySizesConfig || window.lazysizesConfig || {};

		for (prop in lazySizesDefaults) {
			if (!(prop in lazySizesConfig)) {
				lazySizesConfig[prop] = lazySizesDefaults[prop];
			}
		}

		window.lazySizesConfig = lazySizesConfig;

		setTimeout(function () {
			if (lazySizesConfig.init) {
				init();
			}
		});
	})();

	var loader = (function () {
		var preloadElems, isCompleted, resetPreloadingTimer, loadMode, started;

		var eLvW, elvH, eLtop, eLleft, eLright, eLbottom, isBodyHidden;

		var regImg = /^img$/i;
		var regIframe = /^iframe$/i;

		var supportScroll =
			'onscroll' in window && !/(gle|ing)bot/.test(navigator.userAgent);

		var shrinkExpand = 0;
		var currentExpand = 0;

		var isLoading = 0;
		var lowRuns = -1;

		var resetPreloading = function (e) {
			isLoading--;
			if (!e || isLoading < 0 || !e.target) {
				isLoading = 0;
			}
		};

		var isVisible = function (elem) {
			if (isBodyHidden == null) {
				isBodyHidden = getCSS(document.body, 'visibility') == 'hidden';
			}

			return (
				isBodyHidden ||
				(getCSS(elem.parentNode, 'visibility') != 'hidden' &&
					getCSS(elem, 'visibility') != 'hidden')
			);
		};

		var isNestedVisible = function (elem, elemExpand) {
			var outerRect;
			var parent = elem;
			var visible = isVisible(elem);

			eLtop -= elemExpand;
			eLbottom += elemExpand;
			eLleft -= elemExpand;
			eLright += elemExpand;

			while (
				visible &&
				(parent = parent.offsetParent) &&
				parent != document.body &&
				parent != docElem
			) {
				visible = (getCSS(parent, 'opacity') || 1) > 0;

				if (visible && getCSS(parent, 'overflow') != 'visible') {
					outerRect = parent.getBoundingClientRect();
					visible =
						eLright > outerRect.left &&
						eLleft < outerRect.right &&
						eLbottom > outerRect.top - 1 &&
						eLtop < outerRect.bottom + 1;
				}
			}

			return visible;
		};

		var checkElements = function () {
			var eLlen,
				i,
				rect,
				autoLoadElem,
				loadedSomething,
				elemExpand,
				elemNegativeExpand,
				elemExpandVal,
				beforeExpandVal,
				defaultExpand,
				preloadExpand,
				hFac;
			var lazyloadElems = lazysizes.elements;

			if (
				(loadMode = lazySizesConfig.loadMode) &&
				isLoading < 8 &&
				(eLlen = lazyloadElems.length)
			) {
				i = 0;

				lowRuns++;

				defaultExpand =
					!lazySizesConfig.expand || lazySizesConfig.expand < 1
						? docElem.clientHeight > 500 &&
						  docElem.clientWidth > 500
							? 500
							: 370
						: lazySizesConfig.expand;

				lazysizes._defEx = defaultExpand;

				preloadExpand = defaultExpand * lazySizesConfig.expFactor;
				hFac = lazySizesConfig.hFac;
				isBodyHidden = null;

				if (
					currentExpand < preloadExpand &&
					isLoading < 1 &&
					lowRuns > 2 &&
					loadMode > 2 &&
					!document.hidden
				) {
					currentExpand = preloadExpand;
					lowRuns = 0;
				} else if (loadMode > 1 && lowRuns > 1 && isLoading < 6) {
					currentExpand = defaultExpand;
				} else {
					currentExpand = shrinkExpand;
				}

				for (; i < eLlen; i++) {
					if (!lazyloadElems[i] || lazyloadElems[i]._lazyRace) {
						continue;
					}

					if (!supportScroll) {
						unveilElement(lazyloadElems[i]);
						continue;
					}

					if (
						!(elemExpandVal =
							lazyloadElems[i][_getAttribute]('data-expand')) ||
						!(elemExpand = elemExpandVal * 1)
					) {
						elemExpand = currentExpand;
					}

					if (beforeExpandVal !== elemExpand) {
						eLvW = innerWidth + elemExpand * hFac;
						elvH = innerHeight + elemExpand;
						elemNegativeExpand = elemExpand * -1;
						beforeExpandVal = elemExpand;
					}

					rect = lazyloadElems[i].getBoundingClientRect();

					if (
						(eLbottom = rect.bottom) >= elemNegativeExpand &&
						(eLtop = rect.top) <= elvH &&
						(eLright = rect.right) >= elemNegativeExpand * hFac &&
						(eLleft = rect.left) <= eLvW &&
						(eLbottom || eLright || eLleft || eLtop) &&
						(lazySizesConfig.loadHidden ||
							isVisible(lazyloadElems[i])) &&
						((isCompleted &&
							isLoading < 3 &&
							!elemExpandVal &&
							(loadMode < 3 || lowRuns < 4)) ||
							isNestedVisible(lazyloadElems[i], elemExpand))
					) {
						unveilElement(lazyloadElems[i]);
						loadedSomething = true;
						if (isLoading > 9) {
							break;
						}
					} else if (
						!loadedSomething &&
						isCompleted &&
						!autoLoadElem &&
						isLoading < 4 &&
						lowRuns < 4 &&
						loadMode > 2 &&
						(preloadElems[0] || lazySizesConfig.preloadAfterLoad) &&
						(preloadElems[0] ||
							(!elemExpandVal &&
								(eLbottom ||
									eLright ||
									eLleft ||
									eLtop ||
									lazyloadElems[i][_getAttribute](
										lazySizesConfig.sizesAttr
									) != 'auto')))
					) {
						autoLoadElem = preloadElems[0] || lazyloadElems[i];
					}
				}

				if (autoLoadElem && !loadedSomething) {
					unveilElement(autoLoadElem);
				}
			}
		};

		var throttledCheckElements = throttle(checkElements);

		var switchLoadingClass = function (e) {
			var elem = e.target;

			if (elem._lazyCache) {
				delete elem._lazyCache;
				return;
			}

			resetPreloading(e);
			addClass(elem, lazySizesConfig.loadedClass);
			removeClass(elem, lazySizesConfig.loadingClass);
			addRemoveLoadEvents(elem, rafSwitchLoadingClass);
			triggerEvent(elem, 'lazyloaded');
		};
		var rafedSwitchLoadingClass = rAFIt(switchLoadingClass);
		var rafSwitchLoadingClass = function (e) {
			rafedSwitchLoadingClass({ target: e.target });
		};

		var changeIframeSrc = function (elem, src) {
			try {
				elem.contentWindow.location.replace(src);
			} catch (e) {
				elem.src = src;
			}
		};

		var handleSources = function (source) {
			var customMedia;

			var sourceSrcset = source[_getAttribute](
				lazySizesConfig.srcsetAttr
			);

			if (
				(customMedia =
					lazySizesConfig.customMedia[
						source[_getAttribute]('data-media') ||
							source[_getAttribute]('media')
					])
			) {
				source.setAttribute('media', customMedia);
			}

			if (sourceSrcset) {
				source.setAttribute('srcset', sourceSrcset);
			}
		};

		var lazyUnveil = rAFIt(function (elem, detail, isAuto, sizes, isImg) {
			var src, srcset, parent, isPicture, event, firesLoad;

			if (
				!(event = triggerEvent(elem, 'lazybeforeunveil', detail))
					.defaultPrevented
			) {
				if (sizes) {
					if (isAuto) {
						addClass(elem, lazySizesConfig.autosizesClass);
					} else {
						elem.setAttribute('sizes', sizes);
					}
				}

				srcset = elem[_getAttribute](lazySizesConfig.srcsetAttr);
				src = elem[_getAttribute](lazySizesConfig.srcAttr);

				if (isImg) {
					parent = elem.parentNode;
					isPicture =
						parent && regPicture.test(parent.nodeName || '');
				}

				firesLoad =
					detail.firesLoad ||
					('src' in elem && (srcset || src || isPicture));

				event = { target: elem };

				addClass(elem, lazySizesConfig.loadingClass);

				if (firesLoad) {
					clearTimeout(resetPreloadingTimer);
					resetPreloadingTimer = setTimeout(resetPreloading, 2500);
					addRemoveLoadEvents(elem, rafSwitchLoadingClass, true);
				}

				if (isPicture) {
					forEach.call(
						parent.getElementsByTagName('source'),
						handleSources
					);
				}

				if (srcset) {
					elem.setAttribute('srcset', srcset);
				} else if (src && !isPicture) {
					if (regIframe.test(elem.nodeName)) {
						changeIframeSrc(elem, src);
					} else {
						elem.src = src;
					}
				}

				if (isImg && (srcset || isPicture)) {
					updatePolyfill(elem, { src: src });
				}
			}

			if (elem._lazyRace) {
				delete elem._lazyRace;
			}
			removeClass(elem, lazySizesConfig.lazyClass);

			rAF(function () {
				// Part of this can be removed as soon as this fix is older: https://bugs.chromium.org/p/chromium/issues/detail?id=7731 (2015)
				if (!firesLoad || (elem.complete && elem.naturalWidth > 1)) {
					switchLoadingClass(event);
					elem._lazyCache = true;
					setTimeout(function () {
						if ('_lazyCache' in elem) {
							delete elem._lazyCache;
						}
					}, 9);
				}
			}, true);
		});

		var unveilElement = function (elem) {
			var detail;

			var isImg = regImg.test(elem.nodeName);

			//allow using sizes="auto", but don't use. it's invalid. Use data-sizes="auto" or a valid value for sizes instead (i.e.: sizes="80vw")
			var sizes =
				isImg &&
				(elem[_getAttribute](lazySizesConfig.sizesAttr) ||
					elem[_getAttribute]('sizes'));
			var isAuto = sizes == 'auto';

			if (
				(isAuto || !isCompleted) &&
				isImg &&
				(elem[_getAttribute]('src') || elem.srcset) &&
				!elem.complete &&
				!hasClass(elem, lazySizesConfig.errorClass) &&
				hasClass(elem, lazySizesConfig.lazyClass)
			) {
				return;
			}

			detail = triggerEvent(elem, 'lazyunveilread').detail;

			if (isAuto) {
				autoSizer.updateElem(elem, true, elem.offsetWidth);
			}

			elem._lazyRace = true;
			isLoading++;

			lazyUnveil(elem, detail, isAuto, sizes, isImg);
		};

		var onload = function () {
			if (isCompleted) {
				return;
			}
			if (Date.now() - started < 999) {
				setTimeout(onload, 999);
				return;
			}
			var afterScroll = debounce(function () {
				lazySizesConfig.loadMode = 3;
				throttledCheckElements();
			});

			isCompleted = true;

			lazySizesConfig.loadMode = 3;

			throttledCheckElements();

			addEventListener(
				'scroll',
				function () {
					if (lazySizesConfig.loadMode == 3) {
						lazySizesConfig.loadMode = 2;
					}
					afterScroll();
				},
				true
			);
		};

		return {
			_: function () {
				started = Date.now();

				lazysizes.elements = document.getElementsByClassName(
					lazySizesConfig.lazyClass
				);
				preloadElems = document.getElementsByClassName(
					lazySizesConfig.lazyClass +
						' ' +
						lazySizesConfig.preloadClass
				);

				addEventListener('scroll', throttledCheckElements, true);

				addEventListener('resize', throttledCheckElements, true);

				if (window.MutationObserver) {
					new MutationObserver(throttledCheckElements).observe(
						docElem,
						{ childList: true, subtree: true, attributes: true }
					);
				} else {
					docElem[_addEventListener](
						'DOMNodeInserted',
						throttledCheckElements,
						true
					);
					docElem[_addEventListener](
						'DOMAttrModified',
						throttledCheckElements,
						true
					);
					setInterval(throttledCheckElements, 999);
				}

				addEventListener('hashchange', throttledCheckElements, true);

				//, 'fullscreenchange'
				[
					'focus',
					'mouseover',
					'click',
					'load',
					'transitionend',
					'animationend',
					'webkitAnimationEnd',
				].forEach(function (name) {
					document[_addEventListener](
						name,
						throttledCheckElements,
						true
					);
				});

				if (/d$|^c/.test(document.readyState)) {
					onload();
				} else {
					addEventListener('load', onload);
					document[_addEventListener](
						'DOMContentLoaded',
						throttledCheckElements
					);
					setTimeout(onload, 20000);
				}

				if (lazysizes.elements.length) {
					checkElements();
					rAF._lsFlush();
				} else {
					throttledCheckElements();
				}
			},
			checkElems: throttledCheckElements,
			unveil: unveilElement,
		};
	})();

	var autoSizer = (function () {
		var autosizesElems;

		var sizeElement = rAFIt(function (elem, parent, event, width) {
			var sources, i, len;
			elem._lazysizesWidth = width;
			width += 'px';

			elem.setAttribute('sizes', width);

			if (regPicture.test(parent.nodeName || '')) {
				sources = parent.getElementsByTagName('source');
				for (i = 0, len = sources.length; i < len; i++) {
					sources[i].setAttribute('sizes', width);
				}
			}

			if (!event.detail.dataAttr) {
				updatePolyfill(elem, event.detail);
			}
		});
		var getSizeElement = function (elem, dataAttr, width) {
			var event;
			var parent = elem.parentNode;

			if (parent) {
				width = getWidth(elem, parent, width);
				event = triggerEvent(elem, 'lazybeforesizes', {
					width: width,
					dataAttr: !!dataAttr,
				});

				if (!event.defaultPrevented) {
					width = event.detail.width;

					if (width && width !== elem._lazysizesWidth) {
						sizeElement(elem, parent, event, width);
					}
				}
			}
		};

		var updateElementsSizes = function () {
			var i;
			var len = autosizesElems.length;
			if (len) {
				i = 0;

				for (; i < len; i++) {
					getSizeElement(autosizesElems[i]);
				}
			}
		};

		var debouncedUpdateElementsSizes = debounce(updateElementsSizes);

		return {
			_: function () {
				autosizesElems = document.getElementsByClassName(
					lazySizesConfig.autosizesClass
				);
				addEventListener('resize', debouncedUpdateElementsSizes);
			},
			checkElems: debouncedUpdateElementsSizes,
			updateElem: getSizeElement,
		};
	})();

	var init = function () {
		if (!init.i) {
			init.i = true;
			autoSizer._();
			loader._();
		}
	};

	lazysizes = {
		cfg: lazySizesConfig,
		autoSizer: autoSizer,
		loader: loader,
		init: init,
		uP: updatePolyfill,
		aC: addClass,
		rC: removeClass,
		hC: hasClass,
		fire: triggerEvent,
		gW: getWidth,
		rAF: rAF,
	};

	return lazysizes;
});
