Five years ago I implemented "lazy loading" of the 600+ images on my podcast's archives page (I don't like paging, as a rule) over here https://www.hanselminutes.com/episodes. I did it with jQuery and a jQuery Plugin. It was kind of messy and gross from a purist's perspective, but it totally worked and has easily saved me (and you) hundreds of dollars in bandwidth over the years. The page is like 9 or 10 megs if you load 600 images, not to mention you're loading 600 freaking images.
Fast-forward to 2018, and there's the "Intersection Observer API" that's supported everywhere but Safari and IE, well, because, Safari and IE, sigh. We will return to that issue in a moment.
Following Dean Hume's blog post on the topic, I start with my images like this. I don't populate src="", but instead hold the Image URL in the HTML5 data- bucket of data-src. For src, I can use the nothing grey.gif or just style and color the image grey.
<a href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.hanselman.com/626/christine-spangs-open-source-journey-from-teen-oss-contributor-to-cto-of-nylas" class="showCard"> <img data-src="https://images.hanselminutes.com/images/626.jpg" class="lazy" src="https://www.hanselman.com/images/grey.gif" width="212" height="212" alt="Christine Spang's Open Source Journey from Teen OSS Contributor to CTO of Nylas" /> <span class="shownumber">626</span> <div class="overlay title">Christine Spang's Open Source Journey from Teen OSS Contributor to CTO of Nylas</div> </a> <a href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.hanselman.com/625/a-new-sega-megadrivegenesis-game-in-2018-with-1995-tools-with-tanglewoods-matt-phillips" class="showCard"> <img data-src="https://images.hanselminutes.com/images/625.jpg" class="lazy" src="https://www.hanselman.com/images/grey.gif" width="212" height="212" alt="A new Sega Megadrive/Genesis Game in 2018 with 1995 Tools with Tanglewood's Matt Phillips" /> <span class="shownumber">625</span> <div class="overlay title">A new Sega Megadrive/Genesis Game in 2018 with 1995 Tools with Tanglewood's Matt Phillips</div> </a>
Then, if the images get within 50px intersecting the viewPort (I'm scrolling down) then I load them:
// Get images of class lazy const images = document.querySelectorAll('.lazy'); const config = { // If image gets within 50px go get it rootMargin: '50px 0px', threshold: 0.01 }; let observer = new IntersectionObserver(onIntersection, config); images.forEach(image => { observer.observe(image); });
Now that we are watching it, we need to do something when it's observed.
function onIntersection(entries) { // Loop through the entries entries.forEach(entry => { // Are we in viewport? if (entry.intersectionRatio > 0) { // Stop watching and load the image observer.unobserve(entry.target); preloadImage(entry.target); } }); }
If the browser (IE, Safari, Mobile Safari) doesn't support IntersectionObserver, we can do a few things. I *could* fall back to my old jQuery technique, although it would involve loading a bunch of extra scripts for those browsers, or I could just load all the images in a loop, regardless, like:
if (!('IntersectionObserver' in window)) { loadImagesImmediately(images); } else {...}
Dean's examples are all "Vanilla JS" and require no jQuery, no plugins, no polyfills WITH browser support. There are also some IntersectionObserver helper libraries out there like Cory Dowdy's IOLazy. Cory's is a nice simple wrapper and is super easy to implement. Given I want to support iOS Safari as well, I am using a polyfill to get the support I want from browsers that don't have it natively.
<!-- intersection observer polyfill --> <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
Pollyfill.io is a lovely site that gives you just the fills you need (or those you need AND request) tailored to your browser. Try GETting the URL above in Chrome. You'll see it's basically empty as you don't need it. Then hit it in IE, and you'll get the polyfill. The official IntersectionObserver polyfill is at the w3c.
At this point I've removed jQuery entirely from my site and I'm just using an optional polyfill plus browser support that didn't exist when I started my podcast site. Fewer moving parts means a cleaner, leaner, simpler site!
Go subscribe to the Hanselminutes Podcast today! We're on iTunes, Spotify, Google Play, and even Twitter!
Sponsor: Announcing Raygun APM! Now you can monitor your entire application stack, with your whole team, all in one place. Learn more!
© 2018 Scott Hanselman. All rights reserved.