text: const defaultSettings = {
  	elements_selector: "img",
  	container: document,
  	threshold: 300,
  	data_src: "src",
  	data_srcset: "srcset",
  	data_sizes: "sizes",
  	class_loading: "loading",
  	class_loaded: "loaded",
  	class_error: "error",
  	load_delay: 0,
  	callback_load: null,
  	callback_error: null,
  	callback_set: null,
  	callback_enter: null,
  	to_webp: false
  };
  
  var getInstanceSettings = customSettings => {
  	return Object.assign({}, defaultSettings, customSettings);
  };
  
  const dataPrefix = "data-";
  const processedDataName = "was-processed";
  const timeoutDataName = "ll-timeout";
  const trueString = "true";
  
  const getData = (element, attribute) => {
  	return element.getAttribute(dataPrefix + attribute);
  };
  
  const setData = (element, attribute, value) => {
  	var attrName = dataPrefix + attribute;
  	if (value === null) {
  		element.removeAttribute(attrName);
  		return;
  	}
  	element.setAttribute(attrName, value);
  };
  
  const setWasProcessedData = element =>
  	setData(element, processedDataName, trueString);
  
  const getWasProcessedData = element =>
  	getData(element, processedDataName) === trueString;
  
  const setTimeoutData = (element, value) =>
  	setData(element, timeoutDataName, value);
  
  const getTimeoutData = element => getData(element, timeoutDataName);
  
  function purgeElements(elements) {
  	return elements.filter(element => !getWasProcessedData(element));
  }
  
  /* Creates instance and notifies it through the window element */
  const createInstance = function(classObj, options) {
  	var event;
  	let eventString = "LazyLoad::Initialized";
  	let instance = new classObj(options);
  	try {
  		// Works in modern browsers
  		event = new CustomEvent(eventString, { detail: { instance } });
  	} catch (err) {
  		// Works in Internet Explorer (all versions)
  		event = document.createEvent("CustomEvent");
  		event.initCustomEvent(eventString, false, false, { instance });
  	}
  	window.dispatchEvent(event);
  };
  
  /* Auto initialization of one or more instances of lazyload, depending on the 
      options passed in (plain object or an array) */
  function autoInitialize(classObj, options) {
  	if (!options) {
  		return;
  	}
  	if (!options.length) {
  		// Plain object
  		createInstance(classObj, options);
  	} else {
  		// Array of objects
  		for (let i = 0, optionsItem; (optionsItem = options[i]); i += 1) {
  			createInstance(classObj, optionsItem);
  		}
  	}
  }
  
  const replaceExtToWebp = (value, condition) =>
  	condition ? value.replace(/\.(jpe?g|png)/gi, ".webp") : value;
  
  const detectWebp = () => {
  	var webpString = "image/webp";
  	var canvas = document.createElement("canvas");
  
  	if (canvas.getContext && canvas.getContext("2d")) {
  		return canvas.toDataURL(webpString).indexOf(`data:${webpString}`) === 0;
  	}
  
  	return false;
  };
  
  const runningOnBrowser = typeof window !== "undefined";
  
  const isBot =
  	(runningOnBrowser && !("onscroll" in window)) ||
  	/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
  
  const supportsIntersectionObserver =
  	runningOnBrowser && "IntersectionObserver" in window;
  
  const supportsClassList =
  	runningOnBrowser && "classList" in document.createElement("p");
  
  const supportsWebp = runningOnBrowser && detectWebp();
  
  const setSourcesInChildren = function(
  	parentTag,
  	attrName,
  	dataAttrName,
  	toWebpFlag
  ) {
  	for (let i = 0, childTag; (childTag = parentTag.children[i]); i += 1) {
  		if (childTag.tagName === "SOURCE") {
  			let attrValue = getData(childTag, dataAttrName);
  			setAttributeIfValue(childTag, attrName, attrValue, toWebpFlag);
  		}
  	}
  };
  
  const setAttributeIfValue = function(
  	element,
  	attrName,
  	value,
  	toWebpFlag
  ) {
  	if (!value) {
  		return;
  	}
  	element.setAttribute(attrName, replaceExtToWebp(value, toWebpFlag));
  };
  
  const setSourcesImg = (element, settings) => {
  	const toWebpFlag = supportsWebp && settings.to_webp;
  	const srcsetDataName = settings.data_srcset;
  	const parent = element.parentNode;
  
  	if (parent && parent.tagName === "PICTURE") {
  		setSourcesInChildren(parent, "srcset", srcsetDataName, toWebpFlag);
  	}
  	const sizesDataValue = getData(element, settings.data_sizes);
  	setAttributeIfValue(element, "sizes", sizesDataValue);
  	const srcsetDataValue = getData(element, srcsetDataName);
  	setAttributeIfValue(element, "srcset", srcsetDataValue, toWebpFlag);
  	const srcDataValue = getData(element, settings.data_src);
  	setAttributeIfValue(element, "src", srcDataValue, toWebpFlag);
  };
  
  const setSourcesIframe = (element, settings) => {
  	const srcDataValue = getData(element, settings.data_src);
  
  	setAttributeIfValue(element, "src", srcDataValue);
  };
  
  const setSourcesVideo = (element, settings) => {
  	const srcDataName = settings.data_src;
  	const srcDataValue = getData(element, srcDataName);
  
  	setSourcesInChildren(element, "src", srcDataName);
  	setAttributeIfValue(element, "src", srcDataValue);
  	element.load();
  };
  
  const setSourcesBgImage = (element, settings) => {
  	const toWebpFlag = supportsWebp && settings.to_webp;
  	const srcDataValue = getData(element, settings.data_src);
  
  	if (srcDataValue) {
  		let setValue = replaceExtToWebp(srcDataValue, toWebpFlag);
  		element.style.backgroundImage = `url("${setValue}")`;
  	}
  };
  
  const setSourcesFunctions = {
  	IMG: setSourcesImg,
  	IFRAME: setSourcesIframe,
  	VIDEO: setSourcesVideo
  };
  
  const setSources = (element, settings) => {
  	const tagName = element.tagName;
  	const setSourcesFunction = setSourcesFunctions[tagName];
  	if (setSourcesFunction) {
  		setSourcesFunction(element, settings);
  		return;
  	}
  	setSourcesBgImage(element, settings);
  };
  
  const addClass = (element, className) => {
  	if (supportsClassList) {
  		element.classList.add(className);
  		return;
  	}
  	element.className += (element.className ? " " : "") + className;
  };
  
  const removeClass = (element, className) => {
  	if (supportsClassList) {
  		element.classList.remove(className);
  		return;
  	}
  	element.className = element.className.
  		replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").
  		replace(/^\s+/, "").
  		replace(/\s+$/, "");
  };
  
  const callbackIfSet = (callback, argument) => {
  	if (callback) {
  		callback(argument);
  	}
  };
  
  const genericLoadEventName = "load";
  const mediaLoadEventName = "loadeddata";
  const errorEventName = "error";
  
  const addEventListener = (element, eventName, handler) => {
  	element.addEventListener(eventName, handler);
  };
  
  const removeEventListener = (element, eventName, handler) => {
  	element.removeEventListener(eventName, handler);
  };
  
  const addAllEventListeners = (element, loadHandler, errorHandler) => {
  	addEventListener(element, genericLoadEventName, loadHandler);
  	addEventListener(element, mediaLoadEventName, loadHandler);
  	addEventListener(element, errorEventName, errorHandler);
  };
  
  const removeAllEventListeners = (element, loadHandler, errorHandler) => {
  	removeEventListener(element, genericLoadEventName, loadHandler);
  	removeEventListener(element, mediaLoadEventName, loadHandler);
  	removeEventListener(element, errorEventName, errorHandler);
  };
  
  const eventHandler = function(event, success, settings) {
  	const className = success ? settings.class_loaded : settings.class_error;
  	const callback = success ? settings.callback_load : settings.callback_error;
  	const element = event.target;
  
  	removeClass(element, settings.class_loading);
  	addClass(element, className);
  	callbackIfSet(callback, element);
  };
  
  const addOneShotEventListeners = (element, settings) => {
  	const loadHandler = event => {
  		eventHandler(event, true, settings);
  		removeAllEventListeners(element, loadHandler, errorHandler);
  	};
  	const errorHandler = event => {
  		eventHandler(event, false, settings);
  		removeAllEventListeners(element, loadHandler, errorHandler);
  	};
  	addAllEventListeners(element, loadHandler, errorHandler);
  };
  
  const managedTags = ["IMG", "IFRAME", "VIDEO"];
  
  const loadAndUnobserve = (element, observer, settings) => {
  	revealElement(element, settings);
  	observer.unobserve(element);
  };
  
  const cancelDelayLoad = element => {
  	var timeoutId = getTimeoutData(element);
  	if (!timeoutId) {
  		return; // do nothing if timeout doesn't exist
  	}
  	clearTimeout(timeoutId);
  	setTimeoutData(element, null);
  };
  
  const delayLoad = (element, observer, settings) => {
  	var loadDelay = settings.load_delay;
  	var timeoutId = getTimeoutData(element);
  	if (timeoutId) {
  		return; // do nothing if timeout already set
  	}
  	timeoutId = setTimeout(function() {
  		loadAndUnobserve(element, observer, settings);
  		cancelDelayLoad(element);
  	}, loadDelay);
  	setTimeoutData(element, timeoutId);
  };
  
  function revealElement(element, settings, force) {
  	if (!force && getWasProcessedData(element)) {
  		return; // element has already been processed and force wasn't true
  	}
  	callbackIfSet(settings.callback_enter, element);
  	if (managedTags.indexOf(element.tagName) > -1) {
  		addOneShotEventListeners(element, settings);
  		addClass(element, settings.class_loading);
  	}
  	setSources(element, settings);
  	setWasProcessedData(element);
  	callbackIfSet(settings.callback_set, element);
  }
  
  /* entry.isIntersecting needs fallback because is null on some versions of MS Edge, and
     entry.intersectionRatio is not enough alone because it could be 0 on some intersecting elements */
  const isIntersecting = entry =>
  	entry.isIntersecting || entry.intersectionRatio > 0;
  
  const getObserverSettings = settings => ({
  	root: settings.container === document ? null : settings.container,
  	rootMargin: settings.threshold + "px",
  	threshold: 0
  });
  
  const LazyLoad = function(customSettings, elements) {
  	this._settings = getInstanceSettings(customSettings);
  	this._setObserver();
  	this.update(elements);
  };
  
  LazyLoad.prototype = {
  	_manageIntersection: function(entry) {
  		var observer = this._observer;
  		var settings = this._settings;
  		var loadDelay = this._settings.load_delay;
  		var element = entry.target;
  		if (isIntersecting(entry)) {
  			if (!loadDelay) {
  				loadAndUnobserve(element, observer, settings);
  			} else {
  				delayLoad(element, observer, settings);
  			}
  		}
  
  		// Writes in and outs in a data-attribute
  		if (!isIntersecting(entry)) {
  			cancelDelayLoad(element);
  		}
  	},
  	_onIntersection: function(entries) {
  		entries.forEach(this._manageIntersection.bind(this));
  		this._elements = purgeElements(this._elements);
  	},
  	_setObserver: function() {
  		if (!supportsIntersectionObserver) {
  			return;
  		}
  		this._observer = new IntersectionObserver(
  			this._onIntersection.bind(this),
  			getObserverSettings(this._settings)
  		);
  	},
  
  	loadAll: function() {
  		this._elements.forEach(element => {
  			this.load(element);
  		});
  		this._elements = purgeElements(this._elements);
  	},
  
  	update: function(elements) {
  		const settings = this._settings;
  		const nodeSet =
  			elements ||
  			settings.container.querySelectorAll(settings.elements_selector);
  
  		this._elements = purgeElements(Array.prototype.slice.call(nodeSet)); // nodeset to array for IE compatibility
  
  		if (isBot || !this._observer) {
  			this.loadAll();
  			return;
  		}
  
  		this._elements.forEach(element => {
  			this._observer.observe(element);
  		});
  	},
  
  	destroy: function() {
  		if (this._observer) {
  			purgeElements(this._elements).forEach(element => {
  				this._observer.unobserve(element);
  			});
  			this._observer = null;
  		}
  		this._elements = null;
  		this._settings = null;
  	},
  
  	load: function(element, force) {
  		revealElement(element, this._settings, force);
  	}
  };
  
  /* Automatic instances creation if required (useful for async script loading) */
  if (runningOnBrowser) {
  	autoInitialize(LazyLoad, window.lazyLoadOptions);
  }
  
  export default LazyLoad;
  