Skip to content

Instantly share code, notes, and snippets.

@Fallenstedt
Last active April 11, 2018 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fallenstedt/469f0f9b76ae7ef74f3009268390007f to your computer and use it in GitHub Desktop.
Save Fallenstedt/469f0f9b76ae7ef74f3009268390007f to your computer and use it in GitHub Desktop.
Lazy Load with Intersection Observer
<img class="lazy" data-src="/some/big/ass/image/that/takes/forever/to/load.jpg" src="/some/small/blurry/image/that/is/shown/first.jpg" alt="The pic">
/**
* Will search for images and video elements that have a class of 'lazy'
* It will then Target the element's `data-src` property and then
* Lazily load the content with an IntersectionObserver or
* a fallback method for ancient browswers.
* https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
*/
var LazyLoadSingleton = (function(window, document) {
/** The Chached Instance */
var instance;
function init() {
var // Placeholders used for event handler strings.
documentEvents = ['scroll', 'touchmove'],
windowEvents = ['orientationchange', 'resize'],
// Tracks if yall is currently processing. Used for throttling.
// Only relevant if IntersectionObserver is unsupported.
active = 0,
// Placeholder for elements
elements,
// Replaces target attribute value with source attribute, if applicable
replaceAttr = function(node, sourceAttr, targetAttr) {
var v = node.getAttribute(sourceAttr);
if (v) {
node[targetAttr] = v;
node.removeAttribute(sourceAttr);
}
},
// The handler to load the media
loadMedia = function(media) {
if (media.tagName == 'VIDEO') {
console.log('video', media);
[].slice
.call(media.querySelectorAll('source'))
.forEach(function(source) {
console.log(source);
replaceAttr(source, 'data-src', 'src');
});
media.load();
} else {
if (media.parentNode.tagName == 'PICTURE') {
[].slice
.call(media.parentNode.querySelectorAll('source'))
.forEach(function(source) {
replaceAttr(source, 'data-srcset', 'srcset');
});
}
replaceAttr(media, 'data-src', 'src');
replaceAttr(media, 'data-srcset', 'srcset');
}
media.classList.remove('lazy');
elements = elements.filter(function(e) {
return e !== media;
});
},
// A multiple event binding handler.
multiBind = function(obj, handlers, fn, remove) {
handlers.forEach(function(handler) {
remove
? obj.removeEventListener(handler, fn)
: obj.addEventListener(handler, fn);
});
},
// The guts of the lazy loader (now only used when IntersectionObserver is not supported (IE 11))
yall = function() {
if (!elements.length) {
// There are no more elements to lazy load, so we'll unbind everything.
multiBind(document, documentEvents, yall, 1);
multiBind(window, windowEvents, yall, 1);
}
// Check if the lazy loader is active
if (!active) {
active = 1;
setTimeout(function() {
elements.forEach(function(media) {
if (
media.getBoundingClientRect().top <= window.innerHeight &&
media.getBoundingClientRect().bottom >= 0 &&
getComputedStyle(media).display != 'none'
) {
loadMedia(media);
}
});
active = 0;
}, 200);
}
},
removeVideoElements = function(e, type) {
return e.tagName !== 'VIDEO';
};
return function() {
// Everything's kicked off on DOMContentLoaded
multiBind(document, ['DOMContentLoaded'], function() {
// is the user on a mobile device? If so, do not lazy load video.
elements = [].slice.call(document.querySelectorAll('.lazy'));
if (window.matchMedia('(max-width: 640px)').matches) {
//Let video elements use their placeholder images instead. Don't load the video
elements = elements.filter(removeVideoElements);
}
// We're only going to do stuff if we found `.lazy` elements
if (elements.length) {
// This compatibility check has been taken from https://github.com/WICG/IntersectionObserver/blob/gh-pages/polyfill/intersection-observer.js
if (
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype
) {
var mediaObserver = new window.IntersectionObserver(function(
entries,
observer
) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
loadMedia(entry.target);
mediaObserver.unobserve(entry.target);
}
});
});
elements.forEach(function(media) {
mediaObserver.observe(media);
});
} else {
// If IntersectionObserver isn't available, we'll do things the old way.
yall();
multiBind(document, documentEvents, yall);
multiBind(window, windowEvents, yall);
}
}
});
};
}
return {
getInstance: function() {
if (!instance) {
instance = init();
}
return instance;
}
};
})(window, document);
var lazyItUp = LazyLoadSingleton.getInstance();
lazyItUp();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment