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"]');
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:
const targetElement = document.querySelector('[data-js="target"]');
if (targetElement && 'IntersectionObserver' in window) {
// something?
}
This just ensures two things:
- The element we want to look at exists
- 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
};
}
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:
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:
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
};
}
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:
... 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
};
}
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]
};
}
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);
}
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);
}
}
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);
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);
}
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
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?
}
}
}
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)
THANK YOU!! Been searching for instructions like a mad man, for days!
This document is priceless. Again, thank you for your time.
My memory is so poor, I write this stuff mostly for myself. Luckily, that means I forget my own jokes too.