visit
Now you might wonder, What exactly is a root element? Well, a root element is simply an element that is a parent or container element to other elements. Meaning, If we created a
div
in an HTML document and inside this div
we placed a p
text, the div
becomes the direct root element (parent) of the p
text as it is what contains the paragraph.<body>
<div>
<p>Lorem, ipsum.</p>
</div>
</body>
Based on this logic, we can safely say the
body
is also the immediate parent to this div
and also a grandparent to the p
text. But you know what else is the ancestral root element of everything in the DOM? The browser viewing the HTML document becomes a container (root) to whatever area of the webpage that is visible to the browser's viewport (screen) at any time.So in essence, the Intersection Observer API can be used to observe an element to see if that element intersects (meets or passes across) its root element in the DOM or if it simply enters or leaves the browser's viewport. And for the observer to trigger a callback function when this event takes place.Note: A callback function is simply a normal function that is provided to another function as that function's argument (the actual value for its parameter).
Below is an image I've prepared that illustrates an actual Intersection in action, It should give you an idea of how it works, but if it's still unclear, don't sweat it... I'll explain everything in a minute. <body>
<section class="section-1">
<h2>Section 1</h2>
</section>
<section class="section-2">
<img class="img" src="background.jpg" alt="" />
</section>
<section class="section-3">
<h2>Section 3</h2>
</section>
</body>
Now for the CSS, we'll give each section a
height
of 100vh
, center the contents of each section using flex
, then give the image a fixed responsive width and make each section obvious by applying a background colour to separate them. Lastly, we will create a hidden
class that will be responsible for hiding and revealing our content later on using JavaScript.h2 {
font-size: 3rem;
}
.img {
width: 95%;
max-width: 600px;
transition: all 1.5s ease-in;
}
section {
background-color: #dbe6eb;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.section-2 {
background-color: #fff;
}
.hidden {
opacity: 0;
transform: translateX(100%);
}
new IntersectionObserver();
const section = document.querySelector(‘.section-2’);
Then let’s create an observer to observe this
section
element.Const theObserver = IntersectionObserver(callbackFunction, options);
Once we have created an observer, we have to tell the observer what target element to observe using its built-in
observe()
method on the observer. This method receives the target element to be observed as its argument. So let's do just that.theObserver.observe(section);
Let’s go through what we just did now, we first selected a target element to be observed
section
, then created an observer theObserver
, and finally, we told the observer what to observe bypassing the target element to be observed into the observer using the observe()
method. That's it, we have everything set up, the only problem is we have neither defined our callbackFunction
nor the options
object so they are currently undefined. Now, let's define the options that we initially passed into the observer constructor on creation as it is still linked to nothing at the moment. I’ll start with defining the options object (recall that this is used to customise the observer) and then explain each property inside.
Note: Because an object can not be hoisted (used before it is defined), to avoid errors it should always be defined at the top before passing it to the observer, or the actual object itself can be passed as an argument to the Observer when creating the observer.
With that in mind, let's rewrite the JavaScript code we have written so far in the appropriate order.const section = document.querySelector(‘.section-2’);
const options = {
root: null,
threshold: 0.3,
rootMargin: "-100px",
}
const theObserver = new IntersectionObserver(callbackFunction, options);
}
theObserver.observe(section);
This is where we specify what exact root element we want our observed element to intersect against. The root:
root
is usually an ancestor of the target element in the DOM (i.e a container or parent element of the observed element). The value is set to null
if we want the observed element to intersect with the entire browser's viewport (that's the default value). Think of the root element as a rectangular “capturing frame” that the observed target element needs to make contact with.
The threshold:
threshold
is basically the percentage of the observed target that should come into view before it can be considered an intersection. Confused? Well, do you want the target element to completely come into view (become 100% visible) before triggering the callback? or do you want just a fraction of it to be visible in the browser's viewport before running the callback? That's what you have to specify as the threshold
.threshold
receives a numeric value between 0 and 1 which represents the percentage in which the target intersects the root. Meaning 0.1 represents 10%, 0.2 is 20%, 0.5 is 50%, and 1 is 100%. The default value is 0, which means the intersection occurs as soon as the observed element hits even 0px of the root
(about to come into view).rootMargin:
Because the root element is considered to be a rectangular frame (bounding box) with four sides, margins (positive or negative) can be applied to the root element just like in CSS, to grow or shrink its frame of intersection.Recall that the browser's viewport is our
root
element (which is a rectangular frame) and we set the threshold
to 0.3
? that means the intersection should occur when 30% of the observed element comes into view. Now, we also went ahead to set the rootMargin
to -100px
, this will shrink the intersection frame by -100px
and the intersection will no longer occur at our specified 30% threshold
but would instead wait until another 100px of the target element has come into view after the initial 30% (think of it as adding 100px to the 30%).If the margin was set to
100px
the intersection would be triggered while the observed element was still 100px
away from the 30% threshold (the negative margin shrinks the frame of the intersection while the positive margin grows it/pushes it outwards).When a webpage with an Intersection Observer is initially loaded for the first time, the Observer always fires the provided callback function once by default regardless of an actual intersection or not (I know, it's a weird behaviour). When this occurs, the observer passes an
entries
array to the callback function, and this entries
array itself contains an IntersectionObserverEntry
object inside of it. This object contains several properties that describe the intersection between the target element and its root container.Enough talk... let's define the callback function so we can see the object itself.function callbackFunction(entries) {
console.log(entries);
}
target:
This is the actual element that is being observed by the observer for an intersection with the root element.isIntersecting:
This returns a Boolean value of true
if the target element being observed is currently intersecting (if the threshold of the target element has intersected) with the root element or false
if that's not the case.isVisible:
This returns a Boolean value of true
or false
which indicates whether or not the target element being observed is currently visible in the browser's viewport.const imgContent = document.querySelector(".img");
function callBackFunction(entries) {
const [entry] = entries;
if (entry.isIntersecting) {
imgContent.classList.remove("hidden");
} else {
imgContent.classList.add("hidden");
}
}
const [entry] = entries;
entry
variable to make it easier to directly access the properties of interest stored in that object.if (entry.isIntersecting) {imgContent.classList.remove("hidden")}
isIntersecting
property to see if our target element (the target section-2) has intersected with the viewport, If the value is true
we remove the hidden
class that we initially created in our CSS from the image to reveal it (you might be wondering why we are removing a hidden class that we never did add to the image... the else block below is your answer).else {imgContent.classList.add("hidden")}
isIntersecting
value is false
we add the hidden
class to the image, But do you recall that the callback function gets fired once by the Observer when we load the webpage? When this happens the initial entry is passed to our function. Since there is no Intersection, this else block will run because the value is false
, thereby hiding our image on load.Excuse the lag in the recording, my screen recorder was acting up. But as you can see, as we scroll towards the observed section, once 30% of the element comes into view we ought to get an Intersection, but because we set the
rootMargin
to -100px
the Intersection will now occur when the target section scrolls another 100px into view, then an Intersection is triggered and the callback is fired. The image then gets revealed and slides back into its original position as the hidden class gets removed.And as the observed section scrolls out of view (exits) the callback is fired again by the Observer, If you can recall, we discussed how the observer is fired on entry and again fired when exiting the viewport... and since the second time there is no actual intersection, the hidden class is added again and the image is hidden as we scroll out of view.Here is the entire JavaScript code we wrote.const section = document.querySelector(".section-2");
const imgContent = document.querySelector(".img");
const objOptions = {
root: null,
threshold: 0.3,
rootMargin: "-100px",
};
const sectionObserver = new IntersectionObserver(callBackFunction, objOptions);
sectionObserver.observe(section);
function callBackFunction(entries) {
const [entry] = entries;
console.log(entry);
if (entry.isIntersecting) {
imgContent.classList.remove("hidden");
} else {
imgContent.classList.add("hidden");
}
}
Congrats!!! You have successfully implemented a basic Intersection, but there are a few things I didn't get to cover as the article was getting too long. We did not cover how to observe multiple elements, nor did we discuss how to unobserved an element after Intersection. For this, I've made part two of this article where we cover these and also build another webpage where we observe multiple sections and do something for each of their respective Intersection.
And if you were wondering what else you could do with the IntersectionObserver, your imagination is the limit, you could implement an Infinite scrolling page, a lazy loading feature, a sticky menu and so much more. I'll probably make more tutorials covering these features so stay tuned.
Buy me a coffee☕
If you liked my article and found it helpful, you can buy me a coffee using any of the links below.👇
Also, do well to like and follow for more contents. And if you've got any questions or spot any errors... please do well to leave some feedback as this is my first technical article.
Also published at
Disclaimer: The author provides this code and software “AS IS”, without
warranty of any kind, express or implied, including but not limited to fitness for a particular purpose and non-infringement. In no event shall the
author be liable for any claim, damages or other liability in connection with the software or code provided here