Skip to main content
Andrea Verlicchi

Making the web faster and more user-friendly

Intersection Observer and lazy loading of elements

In August 2017 a new exciting browser API finally gained quite wide support: the Intersection Observer API, which we can use to know when two DOM elements intersect, or when a given DOM element enters the browser viewport. The lazy loading of images is just the case.

About 3 years and 500 commits ago, I gave birth to a new vanilla javascript lazy load script, currently known as vanilla-lazyload on npm and cdnjs, and starred by 1,135 people on GitHub.

The switch to Intersection Observer #

As the community around my script started to grow, some members requested it to support the Intersection Observer API, amongst other enhancements.

So I decided to make my script's fans happy, and I did it. The resulting source code is 40% smaller, and vanilla-lazyload is now much faster than its previous version. (by the way, if you know how to measure a script performance precisely, please comment)

How to create an Intersection Observer #

The implementation is quite simple: you create a new Intersection Observer instance specifying a callback function to be called whenever an intersection event occurs, and a couple of options: root and rootMargin, that are:

var myObserver = new IntersectionObserver((entries) => {
    /* do something */
}, {
    root: null,
    rootMargin: "300px 0"
});

Once the intersection observer is created and assigned to a variable, you can start observing on a target element with:

var elementToObserve = document.getElementById('observeMe');
myObserver.observe(elementToObserve);

Creating the observer #

Since I need to test the intersection of a set of images / elements against the scrolling container (the viewport, or any element styled with overflow: scroll), I create a new IntersectionObserver and store it in the LazyLoad property _observer.

this._observer = new IntersectionObserver(onIntersection, {
    root: settings.container === document ? null : settings.container,
    rootMargin: settings.threshold + "px"
});

Notes:

The onIntersection function #

The point is that whenever a DOM element intersects with the scrolling container, the onIntersection function is called. Here goes the function definition:

const onIntersection = (entries) => {
    entries.forEach((entry) => {
        if (!entry.isIntersecting) {
            return;
        }
        let element = entry.target;
        revealElement(element, settings);
        this._observer.unobserve(element);
    });
};

The entries argument is passed all the elements being observed by the observer which calls this function. So we need to cycle through each entry and know whether or not it is intersecting with the root element. Fortunately, the isIntersecting property does this exactly, so:

Observing an element #

Isn't something missing? Yes it is! The code explained until now accomplishes nothing, if we don't tell our observer to observe an element, or more than one in our case. What we need to do is:

this._elements.forEach(element => {
    this._observer.observe(element);
});

Being this._elements the set of elements that LazyLoad has found inside this._container by a specific selector, purged by the ones that are already being processed (to deal with cases of infinite scroll, or other DOM updating cases).

Fallback #

Since not every browser are created equal we need to test the support for IntersectionObserver before we use it. The way to do it is simple:

if (!("IntersectionObserver" in window)) {
    return;
}

In my case, on browsers where IntersectionObserver is not supported, LazyLoad will load all the images at once. Therefore, if you need to load lots of images and your user base is browsing with a consistent share of older browsers, you should use LazyLoad a version less than 9. See changelog for more details.

Conclusion #

That's it! Intersection Observer makes your code much smaller, faster and more legible. If you're not using it already, you should definetly start playing around with it.

And don't forget, if you have a very long page stuffed with images and other content, you should load them lazily using a lazy load script, and your best choice is vanilla-lazyload!