258 lines
7.1 KiB
JavaScript
258 lines
7.1 KiB
JavaScript
/*! 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();
|
|
|