TLDR: Intersection Observer API is amazing and I give a tutorial on how to achieve fade in on scroll using it. Here’s a Codepen showing the code all in one place.

Intersection Observer API

After recently updating my website, I was determined to do as much from the ground up as possible. The beauty of working on personal projects is that you can take your time, think deeply about which directions you want to go and rewrite and refactor again and again to improve your codebase.

I was originally using the Waypoints to handle the triggering of animations on scroll, but I found that it can be a bit buggy and isn’t the most performant way to handle what I needed it for. After doing some research I came across a fairly new addition to Javascript called the “Intersection Observer API”.

I decided to take advantage of the API when going through my template with a fine-tooth comb and attempting to purge all unnecessary libraries and frameworks. For example, I’ve recently stripped out Bootstrap, Waypoints and soon to be Jquery. Modern JS is a powerful thing and it’s seemingly getting better every day so the need for all of this imported bloat is becoming less and less so.

Intersection Observer API

The API’s goal was to make it easier and more efficient to detect the visibility of elements on the page in relation to the viewport. This is usually an expensive task to accomplish and can make pages noticeably slower as a result of trying to compute the position of elements on the page.

 The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport. – MDN

You can use the API to handle things like lazy loading, tracking what content has been viewed by people, stopping videos after the user has scrolled past etc. The main benefit of using this is that you don’t need any external libraries, it’s all done with vanilla JS.

Fade in on scroll

Here’s a quick tutorial on how to get your content to fade in on scroll. The first step is to get your CSS ready, I use SCSS so please keep that in mind.

.loadin {
  opacity: 0;
  transform: translateY(45px);
  transition: transform 0.6s, opacity 0.9s;

  &.loaded {
    opacity: 1;
    transform: translateY(0);
    transition-delay: 0s;

As you can see I have a simple kind of “before and after” type set up, with the “loadin” class that hides the elements, and then the “loaded” class to show them.
We are going to set up the JS so that it will add the “loaded” class to wherever it finds a “loadin” class once it is scrolled into view.

The JS

First things first you want to grab all of the elements that you wish to play with, I do it like this below because I find it more readable than having a single “querySelectorAll” with an absurd amount of classes in. If you only have a few things you want to animate in then you can just use a normal variable with a single “querySelectorAll”.

const elementsToLoadIn = new Set([
 ...document.querySelectorAll('.cb-carousel h2, .cb-carousel .swiper-container'),
 ...document.querySelectorAll('.cb-testimonials .swiper-container'),
 ...document.querySelectorAll('.cb-image-grid h2, .cb-image-grid .cb-image-grid__image'),

Then you simply need to loop through each element and add the “loadin” class.

elementsToLoadIn.forEach((el) => {

Next lets set up the Intersection Observer API. The API allows you to pass a configuration object, you can fine-tune the options to your liking, this is the setup that I use:

const observerOptions = {
 root: null,
 rootMargin: '0px',
 threshold: 0.3,

We set the root to null so that the bounds of the actual document viewport are used. The “rootMargin” value to 0px and the threshold value to 0.7.

Next, we set up the function that we will pass as a callback to the intersection instance when we create that.

function observerCallback(entries) {
 entries.forEach((entry) => {
  if (entry.isIntersecting) {'loaded');

Basically, we are adding the class as soon as the element enters the viewport. Now we need to create a new instance of the API and apply the callback function with the options that we set up previously.

const observer = new IntersectionObserver(observerCallback, observerOptions);

Then finally we need to call the observer instance we just created and tell it what to observe exactly. So we loop through the big node list that we originally created like so:

elementsToLoadIn.forEach((el) => observer.observe(el));

There you have it, pretty simple, right? It’s a lot easier than running through manual checks on scroll events, with better performance too.
Browser support is pretty good, but you will need a polyfill to get broader support for IE11 etc.

Previous post:

Next post:

Leave a Reply

Your email address will not be published. Required fields are marked *