Intersection Observer — The Simplest Way to Animate Your Website on Scroll
Thanks to this gem of an API, most of the legwork is done for you when trying to calculate the position of your element on page scroll — enabling you to capture that moment and choose to animate as needed.
The HTML
Lets start off by defining our three sections of our website however you normally would. Here is my example:
<section class="section" id="about">
<h1>About</h1>
</section><section class="section" id="portfolio">
<h1>Portfolio</h1>
</section><section class="section" id="experience">
<h1>Experience</h1>
</section>
You can think of it as me creating three elements, which are three different sections of my website.
The Javascript
Now we get to the fun part. You can create the Intersection Observer like so:
const observer = new IntersectionObserver(callback, options);
As you can see, it takes in two params, a callback function, and some options. You can think of them this way: the options tell the observer what you want to look out for, and if what it finds meets your criteria, then it will call your callback function.
Here are the options for our example:
const options = {
threshold: [0.4, 0.8] // 0.4 = 40%, 0.8 = 80%
}
Basically I’m telling the observer, hey, whenever you see either 40% of the full element, or 80% of the full element, let me know. These moments are when the element is Intersecting.
As for our callback function:
const callback = (entries) => {
entries && entries.forEach(entry => {
if (entry.isIntersecting) {
this.addClassName('visible');
} else {
this.removeClassName('visible');
}
});
};
There is a list of “entries” that is supposed to be received by our callback function from the IntersectionObserver. Each “entry” represents when an element (or “target” we can also call them) reported a change in its intersection status.
What this means is that our callback function will let us know when our element is “intersecting” and also when it stops intersecting. We can capture these moments by checking the isIntersecting property.
Finally we can put these all together like so:
const options = {
threshold: [0.4, 0.8] // 0.4 = 40%, 0.8 = 80%
}const callback = (entries) => {
entries && entries.forEach(entry => {
if (entry.isIntersecting) {
this.addClassName('visible');
} else {
this.removeClassName('visible');
}
});
};const observer = new IntersectionObserver(callback, options);const target = document.querySelector('#about'); // grab your element however you pleaseobserver.observe(target); // start observing our element!
The CSS
Well the hard part’s done, but as you can see I did show a function called “addClassName” in the above code snippet. This function is just some pseudo code I put that will add the class “visible” and remove it when the element is no longer on the screen. Depending on how you’re developing this, you can add the classname via good ol’ element classlist, or Angular’s renderer2 if you’re like me.
Regardless, what does the css look like then?
.section {
opacity: 0;
transition: opacity 1s ease-out;
will-change: opacity;
}
.section.visible {
opacity: 1;
}
This adds a fun little fade in when the element comes into view. By default, none of the elements will be visible.
Final Result
You can find the full code here with an example here: https://stackblitz.com/edit/angular-ivy-zgpxom?file=src/app/app.component.scss
On a side note, How could I do it without Intersection Observer?
The old way is well, a bunch of Javascript you would write yourself. Good luck trying to wrap your head around the function’s return statement if you haven’t had your coffee yet.
//EVENT LISTENER
document.addEventListener('scroll', function(e) {
const windowHeight = window.innerHeight;
const elementVisible = 20; // 20px essentially
const isAboutPage = this.isPage(windowHeight, elementVisible, 'about'); // have we reached element with id #about if (isAboutPage) {
//do stuff here since we are on the page now
}
}//FUNCTION
isPage(windowHeight: any, elementVisible:any, elementID:string) {
const workElement = document.getElementById(elementID);
const elementTop = workElement?.getBoundingClientRect().top;
return elementTop && (elementTop < windowHeight - elementVisible);
}
To give you the bottom line: here we are manually finding the top of our element, and checking whether the top 20 pixels of it is visible on the current viewport.
Conclusion
That's all for now! I hope you found it enjoyable. Please don't hesitate to leave any questions or feedback in the comments below, as it will assist me in writing better content in the future.