The Intersection Observer (IO) detects when an element enters or leaves the viewport (or a parent element). It can be used to easily add animation on
4 years ago
|4 min read
The Intersection Observer (IO) detects when an element enters or leaves the viewport (or a parent element). It can be used to easily add animation on scroll without external libraries.
IO is asynchronous and much more performant than scroll listeners π.
Btw, if you learn better through videos, I highly suggest this youtube tutorial by Kewin Powell.
Here's a basic example of a fade in animation on scroll using the intersection observer.
In this example we fade in an image on scroll by adding the class fadeIn to it when it enters the viewport. This is the js:
const img = document.querySelector("img")
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("fadeIn")
}
})
}
const options = {}
const myObserver = new IntersectionObserver(callback, options)
myObserver.observe(img)
Easy, right? Let's get started π!
Creating an intersection observer
First, we create an intersection observer by calling its constructor and passing it a callback function and an optional options object.
const myObserver = new IntersectionObserver(callback, options)
The options
options is an object with 3 properties:
const options = {
root: null,
rootMargin: '0px',
threshold: 0
}
In my fade in example, I've returned an empty object {} so the default options will apply. (Same with not return anything. )
- root: default null. it's the viewport. Can be the document or an HTML element. If the root is null, defaults to document.
- rootMargin: default 0px. defines the offsets of each side of the root's bounding box. In other words, positive values reduce the root bounding box and negative values increase it. Try scrolling the 3 boxes in this example.
Similar to CSS's margin syntax: "0px 5px 10px 15px" means top: 0px, right: 5px, bottom: 10px and left: 0px. Accepts px and % only. β 0 is not an accepted value, use 0px or 0% instead.
- threshold: default 0. The threshold is a number between 0 and 1.0. 0 meaning as soon as one pixel is visible, the callback will be run. 1.0 means every pixel needs to be visible before calling the callback. (β If you set the threshold to 1 and the element is bigger than the root, the number won't reach 1 because there will be some parts invisible at all time.)
The callback
The callback function takes a list of entries and an intersection observer as parameter.
const callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
The observer can be used to dynamically add or remove elements to observe. More on it below.
The focus is on the list of entries. There is one entry object for each observed element. It's common practice to use forEach to iterate.
Each entry has the following helpful properties:
- entry.isIntersecting returns a boolean. True means the element is currently intersecting with the root.
- entry.target returns the observed element.
I've used them both in the fadeIn animation:
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("fadeIn")
}
})
}
- entry.boundingClientRect returns the bounds rectangle of the observed element.
- entry.intersectionRatio returns a number between 0.0 and 1.0 which indicates how much of the observed element is actually visible within the root.
Etc. π I've named the most important ones. You can find a list of all the entry properties here.
Select elements to be observed
To select an element to observe, we use the observe() method of our Intersection Observer.
myObserver.observe(img)
And that's it! Now myObserver will detect when img enters or leave the viewport and trigger the callback.
If you want to observe many elements, you have to add them one by one.
myObserver.observe(img1)
myObserver.observe(img2)
myObserver.observe(img3)
Or by giving them a common class and iterate with forEach:
const imgList = document.querySelectorAll(".imgToAnimate")
// setting your observer here
imgList.forEach(img => {
myObserver.observe(img)
})
To stop observing, call unobserve() on the element:
myObserver.unobserve(img)
To stop observing every element at once call disconnect():
myObserver.disconnect()
You can also use those methods in the callback:
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("fadeIn")
// stop observing this element
observer.unobserve(entry.target)
}
})
}
Note: It's a good practice to unobserve elements that you are done playing with.
That's it!
I hope you've enjoyed this short intro on Intersection Observer π.
Source: MDN
Beside animating on scroll, it can be used to improve render speed and First Contentful Paint with lazy loading of scripts and media.
Beyond the basics
Here are a few examples of scroll animations with IO. I'll try to write a blog on each when I find some time π .
Enter & Leave Anim
Scroll To Top
Update current tab on scroll
And more to come π!
Comments
Be the first one to comment.