Intersection Observer in action

October 25, 2017

Intersection Observer? Whaaat!? That sounds scary!

In this post I want to explain my journey through and use case for using Intersection Observer. I will also give you a brief overview of how you can get started.

As the specification says…

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.

What this brand new Web API allows you to do, is checking whether you can visually see the element you are observing within your viewport or any other scrollable parent element.

My use case

What about lazy loading images on the page?

Recently, I have been working on improving the performance and payload size we provide to our users at Live Nation.

There are plenty of images on the Live Nation homepage, and sometimes these images are uploaded at such high quality, that we end up delivering images with unfortunately large file sizes.

I’m pretty sure users don’t really care about having every image, from top to bottom downloaded and ready to be seen. I would say they would like to see the page loading faster.

Image of the Live Nation Website embedded on a mobile phone

For that reason, if you are a user on a mobile device, you will only care about 1, 2, 3… images? Pretty much nothing. There is basically no point in serving users content which probably they may not even scroll to see.

So let’s get into it!

We are using React, which helps us create beautiful and reusable components that we can share within our website. So, I took my Image component and added some code to it.

The first thing I wanted to do was make sure it was going to work and behave as a normal image component after the modification, so I added it a new prop observeOnScroll.

With that in place, I could encapsulate my code and execute it by only passing observeOnScroll. It looked like this in my componentDidMount:

if (!this.props.observeOnScroll) {
return false
} = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const { isIntersecting, intersectionRatio } = entry
if (isIntersecting === true || intersectionRatio > 0) {
this.setState({ intersecting: true }) = null
}, {})
  • Checked whether observeOnScroll was passed to the component, otherwise I won’t let the code continue.
  • Create a new IntersectionObserver, which receives a callback function and a set of options. The callback function executes when your element become visible or invisible, so when the element intercepts the viewport and there are some options you can pass, like for example you can add a margin to the element so you can start taking some actions before the element becomes visible. If you would like to, you can investigate Intersection Observer options further.
  • I tell my observer to observe the element, in this case, the image.

And now? What are we waiting for? Let’s start scrolling the page!

Image of the Live nation website scrolling, images are loading on demand

Once you start scrolling and your elements start intersecting with the viewport, you can see that the callbacks are being fired.

You will receive some intersection entries, each entry describes an intersection change for one observable. That’s why I am looping through them, so if any of them are intersecting I can take some action and finally create my image.

const img = new Image()
img.src = this.props.src
img.onload = this.onLoadImage
img.onerror = this.onLoadImageError

But I want my images to load nicely and preload them it without any visual changes, so they can be ready when I rerender my component with the real image. So, I will listen for the onload event and there I will set loaded state to true and I can finally show my Image. All that, together with a nice blurry animation to distract the user while the load is happening.

And we are done!

Image of Intersection Observer from caniuse website

At the time of writing, as you can see, most of the browsers have support for it and it’s strong enough for you to start implementing it into your website.

See current support.

But what about those browser that doesn’t have support? Okay, what I did was lazy load the polyfill in those cases.

.then(() => /* Render React application now that your Polyfills are ready */)
/** Do feature detection, to figure out which polyfills needs to be imported.
function loadPolyfills() {
const polyfills = []
if (!supportsIntersectionObserver()) {
return Promise.all(polyfills)
function supportsIntersectionObserver() {
return (
'IntersectionObserver' in global &&
'IntersectionObserverEntry' in global && 'intersectionRatio' in IntersectionObserverEntry.prototype

We check the support for Intersection Observer by checking if it is on the window object. If it is, then there is no need for the polyfill, if not, we load our polyfill file into the page.

But as always, there are some difficulties and differences in terms of browser implementation:

Like for example, when I was testing with Samsung Internet browser, it didn’t work! There were no images… But why? I looked at the browser support and everything was green, so why!?

Next step, the debug journey.

I used a real device to be able to reproduce the problem (If you want to know how to debug in real devices, take a look at this awesome guide for remote debugging by Samsung Internet) and after some digging, I found out there was no isIntersecting property on the IntersectionObserverEntry available, so I decided to google it! and found this:

Turns out, Edge didn’t have isIntercepting either, so I needed to check intersectionRatio > 0 as well. According to the specification, it’s supposed to be the same as isIntersecting.

Why use IntersectionObserver

  • In the past, to perform the same action, we could use a document level scroll and resize event. But listening for those can be quite expensive if you end up running large numbers of calculations on them. I recommend you should avoid using them.
  • IntersectionObserver is becoming a standard alternative for those, it requires no libraries and have a good and declarative API.
  • When you are not observing for any changes, just disconnect them. No need to be listening all the time *(In our use case, there was no need to continue listening for an image intersection once loaded, so we disconnect them right after)_.
  • Most modern browsers support Intersection Observer, which is a good sign.

Final Metrics

We were loading so many images off-screen, which were not needed for a first load in our page. It reduced the payload by 793 KB (4,450ms). Pretty impressive right? \U0001F631

Image of first metrics before optimisation

Before, there were 66 request for images from the homepage, which is an excessively large amount, more than anything else. Now there are 21, not that many if we compare.

Image of metrics after optimisation

Content request payload is the big boost here, as you can see, but there are many other important take aways here:

  • Time to load dropped from 4.104s to 3.543s .
  • Start render dropped from 1.579s to 1.080s .
  • Speed index marked 4018 and now it’s 2801 . * And loading was completed at 4.104s and now it’s 3.743s .

What can we do next?

At the moment, we have a lot of performance work to do, but we are sure that removing images from it, will be a big boost to improve in this terms.

Let’s look at the page load

Page load image metrics

As you can see in the figure above, our time to load is pretty big in mobile. If we think about it, we are working with around 30 countries, some of them, don’t even have a proper bandwidth connection. This number can be even more scary when loading the page on those countries! and there is no way we are giving them apart.


Using Intersection Observer is amazing, browser support is decent to start using it and can bring a big boost in terms on improving payload time in your page.

Keep an eye on updates! And remember, go and discover your favourite artists and events worldwide at

Read more about

View all posts →