lamplightdev

Lazy loading Web Components with Intersection Observer

Fri Mar 20 2020

One of the many benefits of adopting Web Components (WC) as your component model of choice is that you can make direct use native web platform features with no modifications, wrappers or build scripts. One such example is using the Intersection Observer API and dynamic imports to load WC only when they're needed on the page. Using this pattern defers the loading of the component source on modern browsers until the tag is visible on the page, so:

In this way we can progressively enhance the user experience not only based on browser features, but also only when a component is actually required. All this can be achieved with just a few lines of code:

// keep a record of the components we've loaded in this way
const imported = {};
// Set up our observer:
let observer = new IntersectionObserver((entries, observerRef) => {
// The callback is run when the visibility of one or more of the elements
// being observed has changed. It's also called on page load.
entries.forEach(async entry => {
//`isIntersecting` will be `true` if any part of the element is currently visible
if (entry.isIntersecting) {
// We are assuming here that your Web Component is located in a file
// named after it's tag name
const name = entry.target.nodeName.toLowerCase();

// Once we've observed this element come into view, we can safely remove
// the observer since we won't need to import the WC code again
observerRef.unobserve(entry.target);

if (!imported[name]) {
// Keep a note of which WCs have been loaded so if we have multiple
// instances we don't import twice
imported[name] = true;

// Let's load the WC code
import(`./${name}.js`);
}
}
});
});

// Observe all components with the desired class
const els = document.querySelectorAll('.dynamic-element');
els.forEach(el => {
observer.observe(el);
});

And that's it. The observer callback is run when the visibility of one or more of the elements being observed has changed. There are 3 scenarios:

Here we're only interested in the first case so isIntersecting is all we need - when that's true we know the component has just come into view and we can import the required code. We can also detach the observer so it's not called again when the element (or others of the same type) come into view again. Note that the observer is always run on page load so that if there's a component on screen when the page is loaded it's immediately imported.

I've added an example on this very page. By the time you've scrolled to the end of the page the browser will have dynamically loaded the following <lamplight-lazy></lamplight-lazy> component (check in devtools to see the lamplight-lazy.js file loaded when you scroll to this point.)

Let me know if you have any other tips for progressive enhancement with Web Components!


Did you enjoy this post?

I'd love to know - send me a message on twitter @lamplightdev or sign up for my occasional newsletter on Web Components and Progressive Enhancement - your email will never be shared with anyone else.