Quick introduction to the Intersection Observer API

These last years browsers vendors have paid more attention to the riddles developers were trying to solve to implement native and more performant solutions.

The new Intersection Observer API is here as a response of developers trying to figure out the best way to detect when an element enters the viewport. Doing this is useful in a lot of cases like infinite scrolling, lazy loading images or animating content for example.

Creating a new observer

First thing you need to do is to instance a new observer passing a callback function that will be executed everytime an element becomes visible and an options object that can alter how the observer will behave.

let observer = new IntersectionObserver(onChange, {
  threshold: [ .25 ]
});

function onChange(changes) {
  // for each element that has become visible
  changes.forEach(change => {
    // add class to element
    change.target.classList.add('visible');
  }
}

The callback function will receive a list of all the intersections detected. Each element on that list is an object with useful information like the DOM element itself, or target from now on.

The options object supports a variety of configurations:

We’re still doing nothing, so let’s actually observe something.

let observer = new IntersectionObserver(onChange);

// start observing an element
observer.observe(document.querySelector('.hidden'));

function onChange(changes) {
  changes.forEach(change => {
    change.target.classList.remove('hidden');
  }
}

Passing a node to the observe method will add it to the Intersection Observer’s list of interest.

You can stop watching an element using the unobserve method.

let observer = new IntersectionObserver(onChange);

observer.observe(document.querySelector('.hidden'));

function onChange(changes) {
  changes.forEach(change => {
    change.target.classList.remove('hidden');

    // stop observing the current target
    observer.unobserve(change.target);
  }
}

When you don’t need the observer anymore you can disconnect it.

let observer = new IntersectionObserver(onChange);

observer.observe(document.querySelector('.hidden'));

function onChange(changes) {
  changes.forEach(change => {
    change.target.classList.remove('hidden');
    observer.unobserve(change.target);

    // thank you, we don't need you anymore
    observer.disconnect();
  }
}

This API also comes with a takeRecords method to trigger the observe action at any time.

Lazy loading images in the future

To show an actual use case let’s do some lazy loading, a practice useful to improve the initial rendering time of a page.

// create observer
let observer = new IntersectionObserver(onChange);

function onChange(changes) {
  changes.forEach(change => {
    // take image url from `data-src` attribute
    change.target.src = change.target.dataset.src;

    // stop observing the current target
    observer.unobserve(change.target);
  }
}

// convert node list to array
const imgs = [ ...document.querySelectorAll('.lazy') ];

// observe each image
imgs.forEach(img => observer.observe(img));

Taking advantage of this native API avoiding scroll handlers will improve performance during navigation, making intersection observers a great addition.

Wrap-up

Since this is super new in the web world it’s not available in any browser yet. At the moment it could possibily be shipped with Chrome 51.

In the mean time you can grab this nice polyfill written by Surma Das from Google or check it out in Chrome Canary. Want to see it in action? Check out this living example by Wilson Page.

To know more I suggest reading the explainer present in GitHub’s API sketch.