DEV Community

Cover image for The IntersectionObserver, for developers who are frightened of arrow functions
Ross Angus
Ross Angus

Posted on

The IntersectionObserver, for developers who are frightened of arrow functions

My JavaScript is basically the equivalent of Victorian Copperplate. Frequently long-winded. Sometimes arcane. A bit much, frankly.

Recently, I had cause to prod about the documentation for the IntersectionObserver and figured I could do better. Let's go!

Grab your target element

First, assign the element you want to target to a variable. I'm using const because I'm terribly modern, don't you know:

const targetElement = document.querySelector('[data-js="target"]');
Enter fullscreen mode Exit fullscreen mode

I tend to use data-js attributes rather than classes or IDs so anyone looking at the markup knows what it's for. One of the limitations of this method is that the selector above won't match something like data-js="target tooltip". Watch out for that.

Bit of insurance

One thing I tend to leave out of my example code is the sort of checks we should ideally make, before doing owt. But let's not do that this time:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {
  // something?
}
Enter fullscreen mode Exit fullscreen mode

This just ensures two things:

  1. The element we want to look at exists
  2. The web browser isn't Internet Explorer

Keep your options open

So now we know everything inside the if statement will only be read by the cool web browsers, let's create the options object.

It's a collection of arguments which will be passed to the IntersectionObserver function. Why is it an object? Because passing arguments as individual data points is SO OLD FASHIONED. Everything is an object in 2020. Even your mother is an object.

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {
    root: Document.documentElement,
    rootMargin: '0px',
    threshold: 0
  };

}
Enter fullscreen mode Exit fullscreen mode

So there's three name / value pairs within options:

Root

This is the element which owns the scroll bar we're concerned with. The nice thing about this is that we can observe the scrolling of any element we want - not just the HTML tag. However, if we never define this part at all, then the browser will just assume we mean the HTML tag. So setting it to Document.documentElement is a little redundant.

rootMargin

This is a CSS-like value for a gutter we can create around the scrolling object. Note that the value is 0px rather than the more elegant 0. This is intentional. Apparently JavaScript cares passionately about what kind of nothing it's getting.

Because it's CSS-like, you could also specify it as 10px 20px 30px 40px or 50px 20px or even 20% 0% 50%.

Let's say your page looked like this:

A diagram of a page being larger than the viewport, with a footer element at the bottom, out of view.

The red area above represents root rootMargin of (say) 25%. Let's say the user scrolls down the page until the footer crosses the red margin boundary:

Now the footer is visible, but also is on the edge of the red boundary.

That's the point where IntersectionObserver would fire.

If you don't define rootMargin, then it defaults to zero.

threshold

Useful as rootMargin is, what if you wanted IntersectionObserver to fire when the bottom of your element crossed the bottom of the scrolling element? This is where threshhold shines. In the example above, you'd set options as this:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {
    root: Document.documentElement,
    rootMargin: '0px',
    threshold: 1
  };

}
Enter fullscreen mode Exit fullscreen mode

Threshold takes a value between zero and one which represents how much of your target element passes over the scrolling border before IntersectionObserver fires.

Let's say you had an image like this:

A cat, with the cat's face in the centre of the frame.

... and you wanted the image to briefly swap out for one where the cat was blinking, but only when the image was 60% on screen. In order to get IntersectionObserver to fire, you'd configure threshold like this:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {
    threshold: 0.6
  };

}
Enter fullscreen mode Exit fullscreen mode

Notice that in the above code, I've removed the other two name / value pairs, because both were set to the default. I guess one benefit of everything (including your kin) being an object is that you don't need to pass empty arguments, in order to ensure that the right data reaches the right part of the function. Perhaps those modern kids were right all along.

But that's not all threshold can do! You can also pass it an Array (Arrays are like God in that you need to type them with a capital letter, or they get very cross). That Array could contain a bunch of values between zero and one and IntersectionObserver would fire after every one. Like this:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {
    threshold: [0, 0.1, 0.2, 1]
  };

}
Enter fullscreen mode Exit fullscreen mode

If you don't pass any value for threshold, then IntersectionObserver will fire as soon as the first pixel of your object crosses the border.

That new Object smell

Now it's time to create our lovely new IntersectionObserver. It's a beautiful moment. No need to feel shy. Look:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {threshold: 1};
  // V Look down V
  const observer = new IntersectionObserver(somethingHappened, options);

}
Enter fullscreen mode Exit fullscreen mode

The object takes two parameters, the name of a function which will be called if it's triggered and the options object which we've just covered. You remember options, right? I literally just introduced you. This second parameter is optional - you can leave it out, as long as you want to set the scrolling element to be the whole page, the gutter around the scroll to be zero and you want IntersectionObserver to fire as soon as the first pixel of your chosen element makes it onto the screen.

You might have noticed we've not told IntersectionObserver which element on the page it needs to watch. That bit comes later. For now, we're just setting up the object.

Let's add the function which will fire if everything works:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {threshold: 1};
  const observer = new IntersectionObserver(somethingHappened, options);
  function somethingHappened(event) {
    console.log(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

You're probably wondering where event comes from. It's probably best you don't ask. Just assume IntersectionObserver slipped it into your pocket when you weren't watching, like a Werther's Original.

Who observes the observer?

The above code will do absolutely nothing, because all we've done is set up variables (well, constants) and functions. We need to add this line, in order to get IntersectionObserver to actually watch our chosen element:

observer.observe(targetElement);
Enter fullscreen mode Exit fullscreen mode

This is the bit which connects all of our setup of IntersectionObserver with the element we want to point it at.

This way, you could set up the IntersectionObserver once, then call it on a bunch of different elements on the page, if you wanted to.

Here's that line in context:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {threshold: 1};
  const observer = new IntersectionObserver(somethingHappened, options);
  function somethingHappened(event) {
    console.log(event);
  }
  observer.observe(targetElement);
}
Enter fullscreen mode Exit fullscreen mode

Think of it as an event listener for your scroll bar.

Premature

Here's the weird thing: if you run the above code, you'll immediately get a console log, no scrolling required. So what's the deal? Here's the deal: IntersectionObserver is going to blurt out a whole bunch of data at you when it first loads, then again, once something else significant occurs.

By all means, poke about at what's in the log. Here's the important bit:

event[0].isIntersecting
Enter fullscreen mode Exit fullscreen mode

If this part of the object returns true, then you can conclude your element has crossed the boundary. Which means you can stuff it in an if condition:

const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {

  const options = {threshold: 1};
  const observer = new IntersectionObserver(somethingHappened, options);
  function somethingHappened(event) {
    if (event[0].isIntersecting) {
      // I dunno - do something, I guess?
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that this event will fire once when you scroll down the page, but will also fire a second time if the element appears inside the viewport when you scroll back up again. It won't fire when the element leaves the viewport. Hope this is useful.

Top comments (2)

Collapse
 
lancepierre10 profile image
Lance Pierre

THANK YOU!! Been searching for instructions like a mad man, for days!
This document is priceless. Again, thank you for your time.

Collapse
 
rossangus profile image
Ross Angus

My memory is so poor, I write this stuff mostly for myself. Luckily, that means I forget my own jokes too.