IntersectionObserver is a fairly new web API that makes lazy loading images extremely easy. How easy is to to lazy load with IntersectionObserver? You can do it in about ≈20 lines of code if you don't need a polyfill. Yes 20. You'll need a polyfill though so that selling point is kinda .... meh haha.
Update! Introducing IOLazy Library
I turned this post into a small JavaScript library to lazy load images. You can find the demo for the library over at IOlazy Demo page
File | Size in KB | Gzip Size |
---|---|---|
iolazy.min.js | ≈1.6KB | ≈0.846kB |
using polyfill.io polyfill for intersection observer | ≈6.3KB | ≈1.93KB |
Totals | ≈2.776KB |
Browser Support
As of this post being published Edge 15 (released 11 April 2017 with Windows 10 Creators Update), Chrome 51 and Opera 38 support it without a polyfill. Firefox has this enabled behind a flag.
IntersectionObserver Javascript
As I mentioned above using the IntersectionObserver API to lazy load images results in a small bit of Javascript. IntersectionObservers basically tell the browser "don't load this until it comes into or around the browsers viewport". If this is faster than the other lazy load solutions I don't know I haven't tested it out so you'll have to test this yourself to see.
I've taken the example from Quick Introduction to the Intersection Observer API and made a few changes.
Many of the tutorials from 2016 haven't updated their examples to an API change that check for .isIntersecting
Yes, IOs now emit a change on init. You need to check for change.isIntersecting in your callback.
— Surma (@DasSurma) May 23, 2017
That also introduces an issue in Microsoft Edge 15 since it doesn't have an .isIntersecting
property. Nolan Lawson wrote a another small polyfill we can place in our intersectionobserver lazy load file. You can find that here at
IntersectionObserver issue #211 comment by Nolan Lawson
We'll follow this pattern to create our Observer. Note I'm using es6 so you may need a build step/compilation step based on your browser support.
- set a variable with a node list of all our image elements we want to lazy load
- Create a new IntersectionObserver, add a callback and options to the new IntersectionObserver
- get the data-src attribute from all our lazyload image candidates in our callback
- If our image is within our Threshold we'll apply our image candidate from #3 to our image src attribute.
// small polyfill for Microsoft Edge 15 isIntersecting property
// see https://github.com/WICG/IntersectionObserver/issues/211#issuecomment-309144669
if ('IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype &&
!('isIntersecting' in IntersectionObserverEntry.prototype)) {
Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', {
get: function () {
return this.intersectionRatio > 0
}
})
}
// #1 using the spread operator get all our images with the class 'lazyload'
const imgElements = [ ...document.querySelectorAll( '.lazyload' ) ];
// #2 create our new IntersectionObserver followed by our callback and any options
// 'onChange' is our callback. 'threshold' is our options - the percentage our image is visible.
let observer = new IntersectionObserver( onChange, {
threshold: [ .15 ],
});
// #3 Our callback to grab all our data-src attributes when they become visible.
// This comes from our threshold set above in our options
function onChange( changes ) {
// for each element that becomes visible
changes.forEach( change => {
// Here is the API change mentioned in the linked tweet above
if ( change.isIntersecting ) {
// added a class to our 'target'. This is the visible image element from #1
change.target.classList.add( 'visible' );
// the visible image element we'll grab our image to load from the data-src attribute
// and apply it to the src attribute
change.target.src = change.target.dataset.src;
// stop observing this element
observer.unobserve( change.target );
}
});
}
// Here is where we'll 'observe' our elements to lazyload.
const createObserver = function () {
// for each of our image elements
imgElements.forEach( img => {
// observe the image currently visible and apply #3
observer.observe( img );
});
};
// We'll listen for the 'load' event. Once that has fired we'll apply the function we created
// above called 'createObserver'.
window.addEventListener("load", () => {
createObserver();
}, false);
Lazyload HTML markup
The HTML markup needed is rather small too. We'll need to add in a data-src
attribute with the image we'll load once it comes into our threshold defined in our Intersection Observer.
<img class="lazyload" data-src="/your/file.jpg" src="" alt="an awesome bear" >
Making Sure Images Load
If your image tag has an empty src
attribute and there is no alt
text then FireFox will not load the image.
Easiest fix for this is to have Alt text. In cases you are using a figure
element with figcaption that negates the use of alt text then you should use a base64 encoded 1px gif - or a really small low quality placeholder image.
Polyfills for IntersectionObserver
Now on to getting this to work in browsers with no support yet.
That above one is nice if you have have one off's or you have a build step, use NPM with Browserfiy or Wepback.
The easiest one to use would be the one from Polyfill.io. That one is just inserting a script tag into your document with the features query string set to IntersectionObserver
https://cdn.polyfill.io/v2/polyfill.js?features=IntersectionObserver
Check out polyfill.io! It could really simplify your polyfill needs!
Live Demo
There is a demo located here.
There willl be some lorem ipsum text followed by an image that will lazy load.
You can open up your console and see the console logs I've placed in the script to tell you when an image is visible and when the visible class was applied.