Last active
April 11, 2018 21:00
-
-
Save Fallenstedt/469f0f9b76ae7ef74f3009268390007f to your computer and use it in GitHub Desktop.
Lazy Load with Intersection Observer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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"> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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