The IntersectionObserver, for developers who are frightened of arrow functions
Published: 2020-08-25 07:32:45

---
title: The IntersectionObserver, for developers who are frightened of arrow functions
published: true
description: A quick run through of the IntersectionObserver in JavaScript.
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/aa4qtk2d07dqx4zoas7h.jpg
---

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`](https://caniuse.com/#feat=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:

```js
const targetElement = document.querySelector('[data-js="target"]');
```
I tend to use `data-js` attributes rather than classes or `ID`s 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:

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

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.

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

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

}
```
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.](https://dev-to-uploads.s3.amazonaws.com/i/zyk99och3ca4vjujohnl.png)

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.](https://dev-to-uploads.s3.amazonaws.com/i/w9at8ymgvttrc33csc9f.png)

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:

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

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

}
```
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.](https://dev-to-uploads.s3.amazonaws.com/i/x5tgl2tau123ozfuf6a8.jpg)

... 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:

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

  const options = {
    threshold: 0.6
  };

}
```
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:


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

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

}
```
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:

```js
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);

}
```
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:
```js
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);
  }
}
```
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:

```js
observer.observe(targetElement);
```
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:

```js
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);
}
```
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:

```js
event[0].isIntersecting
```
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:

```js
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?
    }
  }
}
```
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.