/*! lazyload - v2.1.1 - 2018-04-01 * https://github.com/13twelve/lazyload * Copyright (c) 2018 * License: MIT * Author: Mike Byrne @13twelve https://github.com/13twelve */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], 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(); } else { // Browser globals (root is window) root.lazyLoad = factory(); } }(this, function () { var options = { pageUpdatedEventName: 'page:updated', // how your app tells the rest of the app an update happened elements: 'img[data-src], img[data-srcset], source[data-srcset], iframe[data-src], video[data-src], [data-lazyload]', // maybe you just want images? rootMargin: '0px', // IntersectionObserver option threshold: 0, // IntersectionObserver option maxFrameCount: 10, // 60fps / 10 = 6 times a second }; // set up var frameLoop; var frameCount; var els = []; var elsLength; var observer; var checkType; /** * Converts HTML collections to an array * @private * @param {Array} array to convert * a loop will work in more browsers than the slice method */ function _htmlCollectionToArray(collection) { var a = []; var i = 0; for (a = [], i = collection.length; i;) { a[--i] = collection[i]; } return a; } /** * Checks if an element is in the viewport * @private * @param {Node} element to check. * @returns {Boolean} true/false. */ function _elInViewport(el) { el = (el.tagName === 'SOURCE') ? el.parentNode : el; var rect = el.getBoundingClientRect(); return rect.bottom > 0 && rect.right > 0 && rect.left < (window.innerWidth || document.documentElement.clientWidth) && rect.top < (window.innerHeight || document.documentElement.clientHeight); } /** * Removes data- attributes * @private * @param {Node} element to update */ function _removeDataAttrs(el) { el.removeAttribute('data-src'); el.removeAttribute('data-srcset'); el.removeAttribute('data-lazyload'); } /** * On loaded, removes event listener, removes data- attributes * @private */ function _loaded() { this.removeEventListener('load', _loaded); _removeDataAttrs(this); } /** * Update an element * @private * @param {Node} element to update */ function _updateEl(el) { var srcset = el.getAttribute('data-srcset'); var src = el.getAttribute('data-src'); var dlazyload = el.getAttribute('data-lazyload') !== null; // if (srcset) { // if source set, update and try picturefill el.setAttribute('srcset', srcset); if (window.picturefill) { window.picturefill({ elements: [el] }); } } if (src) { // if source set, update el.src = src; } if (dlazyload) { el.setAttribute('data-lazyloaded',''); el.removeEventListener('load', _loaded); _removeDataAttrs(el); } } /** * The callback from the IntersectionObserver * @private * @entries {Nodes} elements being observed by the IntersectionObserver */ function _intersection(entries) { // Disconnect if we've already loaded all of the images if (elsLength === 0) { observer.disconnect(); } // Loop through the entries for (var i = 0; i < entries.length; i++) { var entry = entries[i]; // Are we in viewport? if (entry.intersectionRatio > 0) { elsLength--; // Stop watching this and load the image observer.unobserve(entry.target); entry.target.addEventListener('load', _loaded, false); _updateEl(entry.target); } } } /** * Loops images, checks if in viewport, updates src/src-set * @private */ function _setSrcs() { var i; // browser capability check if (checkType === 'really-old') { elsLength = els.length; for (i = 0; i < elsLength; i++) { if (els[i]) { _updateEl(els[i]); _removeDataAttrs(els[i]); } } els = []; } else if (checkType === 'old') { // debounce checking if (frameCount === options.maxFrameCount) { // update cache of this for the loop elsLength = els.length; for (i = 0; i < elsLength; i++) { // check if this array item exists, hasn't been loaded already and is in the viewport if (els[i] && els[i].lazyloaded === undefined && _elInViewport(els[i])) { // cache this array item var thisEl = els[i]; // set this array item to be undefined to be cleaned up later els[i] = undefined; // give this element a property to stop us running twice on one thing thisEl.lazyloaded = true; // add an event listener to remove data- attributes on load thisEl.addEventListener('load', _loaded, false); // update _updateEl(thisEl); } } // clean up array for (i = 0; i < elsLength; i++) { if (els[i] === undefined) { els.splice(i, 1); } } // reset var to decide if to continue running elsLength = els.length; // will shortly be set to 0 to start counting frameCount = -1; } // run again? kill if not if (elsLength > 0) { frameCount++; frameLoop = window.requestAnimationFrame(_setSrcs); } } else if (checkType === 'new') { observer = new IntersectionObserver(_intersection, { rootMargin: options.rootMargin, threshold: options.threshold, }); elsLength = els.length; for (i = 0; i < elsLength; i++) { if (els[i] && els[i].lazyloaded === undefined) { observer.observe(els[i]); } } } } /** * Gets the show on the road * @private */ function _init() { // kill any old loops if there are any if (checkType === 'old') { try { cancelAnimationFrame(frameLoop); } catch(err) {} } else if (checkType === 'new') { try { observer.disconnect(); } catch(err) {} } // grab elements to lazy load els = _htmlCollectionToArray(document.querySelectorAll(options.elements)); elsLength = els.length; frameCount = options.maxFrameCount; // go go go _setSrcs(); } /** * GO GO GO * @public * @param {object} options (see readme) */ var lazyLoad = function(opts) { for(var item in opts) { if(opts.hasOwnProperty(item)) { options[item] = opts[item]; } } if(!('addEventListener' in window) || !window.requestAnimationFrame || typeof document.body.getBoundingClientRect === undefined) { checkType = 'really-old'; } else if ('IntersectionObserver' in window) { checkType = 'new'; } else { checkType = 'old'; } _init(); if (options.pageUpdatedEventName) { document.addEventListener(options.pageUpdatedEventName, _init, true); } }; return lazyLoad; })); lazyLoad();