JavaScript has 3 observer based APIs, one being Intersection and other two being Resize Observer and Mutation Observer. Intersection Observer in my opinion is the most useful because of how easy it makes things like infinite scrolling, lazing loading images, and scroll based animations. In JavaScript, attaching an event listener on 'scroll' to constantly trigger callback function on scroll can be performance-intensive, and if may end up having a sluggish user experience. Intersection Observer API helps build a better and well performant UI.
Intersection observer API helps detect the visibility of an element, i.e. if it’s in the current viewport, and also the relative visibility of two elements in relationship to each other - triggering a callback function.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#page-bottom {
background-color: #dbe6eb;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.hidden {
opacity: 0;
transform: translateX(100%);
}
</style>
</head>
<body>
<div id="page-bottom">
<h2>Section without image loaded.</h2>
</div>
<img class="lazy-load" data-src="image.jpg" alt="A beautiful scene">
<img class="lazy-load" data-src="image2.jpg" alt="Another beautiful scene">
<script>
document.addEventListener('DOMContentLoaded', () => {
let lazyImages = document.querySelectorAll('.lazy-load');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let image = entry.target;
image.src = image.getAttribute('data-src');
image.onload = () => image.removeAttribute('data-src');
observer.unobserve(image);
}
});
}, {
rootMargin: '0px',
threshold: 0.1 // Adjust threshold based on requirement
});
const pageBottom = document.querySelector('#page-bottom');
lazyImages.forEach(image => {
observer.observe(image);
});
});
</script>
</body>
</html>
We can see in the above image, when the page loaded image has not been loaded(can be verified in network tab). As we'll scroll down, root(document) intersect with images(target) and it loads images. This is an example how lazy-loading can be implemented using Intersection observer API.
To use Intersection Observer, we need to first create a new observer, which takes two parameters: An object with the observer’s options, and the callback function that we want to execute whenever the element we’re observing (known as the observer target) intersects with the root (the scrolling container, which must be an ancestor of the target element).
const intersectionHandler = (entries) =>{
entries.forEach(entry => {
if (entry.isIntersecting) {
//code that depends on intersection
}
});
}
const option = {
root: document.querySelector('root-div'),
rootMargin: '0px',
threshold: 0.1 // Adjust threshold based on requirement
}
const observer = new IntersectionObserver(intersectionHandler, option)
When we’ve created our observer, we need to create a observer
to instruct it to watch a target element:
const targetEl = document.querySelector('target-div')
observer.observe(targetEl)
- You can assume
root
as the outer rectangle, or the rectangle within which you want to observe for an intersection.- The root element can be any parent element or body or document or browser viewport.
- If you specify root as null or skip denfining in the option object, the browser viewport will be the root element.
target
is the element which you are watching.threshold
gives you the extent to which the overlap should occur.rootMargin
is very similar to CSS margin that applied to the root before the extent of the intersection is calculated.
The rootMargin
is like adding CSS margins to the root element. It can take positive and negative values as well.
Using rootMargin
as negative value, element can technically be classed as "intersecting" even when it is out of view, refer Diagram 6 for details.
Default value for
rootMargin
is 0px.
The threshold
can accept a value between 0 and 1 and represents the percentage of the element that must be visible/within the root bounds for it to be considered intersecting.
By default this is set to 0 which means as soon as any part of the element is visible it will be considering intersecting.
You can also pass an array to threshold which means that the Intersection Observer will fire each time your element passes one of the thresholds passed to it.
option = {
root: document.querySelector('root-div'),
rootMargin: '0px',
threshold: [0, 0.25, 0.5, 0.75, 1]
}