Updating jQuery-based Lazy Image Loading to IntersectionObserver
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="/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="/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="/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="/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>
Polyfill.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!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
...yes, I am old ;-)
But as far as page rendering speed, my understanding is that the page won't render until all the required resources (including the images) are present. By initially rendering with a placeholder (particularly, the same placeholder over and over), the page is able to render sooner. And if the images are styled to use the same dimensions regardless of whether it's the placeholder vs. the "for reals" image, then there shouldn't be any need to redraw the entire page.
And getting the page to render quickly is important for SEO. If Google thinks the page is slow enough to create a "bad" user experience, they'll drop the page in the rankings.
(Now if you say, "To heck with Google! Who put them in charge?" Well... I don't disagree with the sentiment, though making Google happy is, at present, a good move from a practical perspective.)
Comments are closed.
Coding for the Web gets better and better.
Also learned that let is usable now, if you can get away with not supporting IE10 and only the most recent version of Safari (still using function scope totally defeated the purpose). https://caniuse.com/#search=let
I'd probably still wait a bit longer before using let on a website.