DEV Community

Cover image for Animating contents on scroll Using the intersection observer API
EstherIdabor
EstherIdabor

Posted on

Animating contents on scroll Using the intersection observer API

Introduction

A common feature you find in most modern web pages and applications are sleek and eye-catching animations that come into play as you scroll through the webpage. This leaves visitors or users with an opulent and grandiose feel of the page or application.
Triggering animations on an element as a page is scrolled entails having information about the position and visibility of that element relative to the viewport.
Previously to observe for visibility of a target element on a certain position of the web page or viewport, the Element.getBoundingClientRect() (gives information about the size and positioning of an element within the webpage), and the windows scroll event were used. However, using that comes with reduced performance and a bad user experience when you have to do that for multiple elements. With the observer intersection API, you can do that without worrying about jeopardizing the performance of the webpage. Now let's discuss the intersection observer API.

Prerequisite

  • Understanding of HTML and CSS.
  • Basic knowledge of JavaScript.

What is the Intersection Observer API?

The intersection observer API allows your code to observe or monitor changes based on how a particular target element intersects with an ancestor or root element. For the purpose of understanding the above statement, take the root element to be the viewport.
The viewport is the visible area of the web page, you can decide to apply styles to the contents of your web page as you bring or take it out of the viewport. One way to go about that is by setting an intersection observer, such that your code performs a task at a set percentage of the intersection of the target element with the root element(the viewport). To understand better consider the example below.
Suppose you tell a group of individuals that they would get $200 each from running on a field, but then a person can only be given that amount of money when they run at least 200 km from the starting point, you are setting an observer.

Card-animation

The above-animated cards display different ways an AI company can help manage client projects. As the page is scrolled, the cards get animated, drawing a visitor's attention to the contents of a card, and as they advance out of the viewport, the animation disappears.
If you follow along with this article you will learn how to trigger animations using the intersection observer API like the video above and apply similar logic to your project.

Creating the web page

The HTML

First, you start by creating a container for the cards and giving it a class of .card-container. Then create each individual card wrapped in a div and give it a class name of .card, each card should be given a unique class name as well since they will all have different background-color during animation.




<div class="card-container" id="cards">
      <div class="card card__1">
        <div>
          <h2 class="secondary-heading">Supervisor</h2>
          <p class="regular-text">
            Monitors activity to identify project roadblocks
          </p>
        </div>
        <div class="img-div">
          <img src="images/icon-supervisor.svg" alt="" />
        </div>
      </div>
    <div class="card card__2">
      <div>
        <h2 class="secondary-heading">Team Builder</h2>
          <p class="regular-text">
            Scans our talent network to create the optimal team for your
            project
          </p>
      </div>
        <div class="img-div">
          <img src="images/icon-team-builder.svg" alt="" />
        </div>
      </div>  
  ....... 
</div>   


Enter fullscreen mode Exit fullscreen mode

The CSS

After creating the markup, proceed to style each <div>to be in the form of a card using the styles below.



This might seem like a lot, but carefully going through it is necessary to understand how the animation works.

The class .show created above with the CSS ensures that when it is present together with the class .card, the width of the card and the colour of the text change. The background colour of each card will be changed as well to different colours.

Dynamically adding and removing the class .show creates the animation.

This is a repo to the source code, you can choose to download just the HTML and CSS and follow along with the JavaScript

Implementing the Intersection Observer API

To create an intersection observer object, you call the constructor function.
const observer = new IntersectionObserver(callback, options);
The constructor function takes in two arguments

  • A callback function: A callback function is a function that is executed when a particular condition is met.
  • An options object: The options object defines the properties of the intersection observer and custom properties upon which the callback is fired.

  • The option objects

The options object is always created with two properties: the root and the threshold.

  • The root property

This is the element the target will intersect with to fire the callback. When the root is set to null, it automatically defaults to the viewport. The root can be anything as long as it is an ancestor of the target element and the target element is a descendant of it.

  • The Threshold

This is the percentage of the intersection at which the observer callback is fired. The value ranges between 0 to 1, representing 0 to 100%.
If you set the threshold of intersection to 0.3, you are setting the callback function to fire whenever 30% of the target element intersects with the root element. You can have an array of thresholds such as [0.25, 0.5], this simply means the callback will fire anytime 25% and 50% of the target element intersects with the root element.

  • The root margin

When present, it invisibly adds a margin to the root element. It is very similar to the CSS margin property and can shrink or grow the size of the root element, it can either be a positive or a negative value.
A value of 100px means the root will grow by 100px, which also means when the target element is 100px away from the set threshold, it is already considered intersecting. A negative value, for example -50px means the root will shrink by 50px. As you scroll, when the target is 50px beyond the threshold, it is considered intersecting, and the callback is fired.



const cardOptions = {
 root: null,
  threshold: 0.5,
  rootMargin: "-50px",
};



Enter fullscreen mode Exit fullscreen mode

The Observer Callback



const cardCallback = function (entries, observer) {
//if the threshold is an array
        for (entry of entries){
        // // code block to be executed
}
         // if the threshold is just one
    const [entry] = entries; // this or
   //const entry = entries[0] 


  if (entry.isIntersecting) {
   //Do something
  }
};


Enter fullscreen mode Exit fullscreen mode

The callback specifies what happens when the target is intersecting and when it stops intersecting with the root element.
It receives two arguments, an array consisting of intersection observer entries named the entries and the reference to the observer object, for this article you won’t need the second argument but it's important to know it exists. The length of the entries array is dependent on the threshold, for every threshold an intersection observer entry exists and it stores information about the intersection of a target element on its root container, so if you have three different thresholds, you will have an array made of three different intersection observer entries observing for the different thresholds.
The callback is fired immediately after the DOM is loaded and in two instances, when the target element starts intersecting and when the target is no more intersecting. However, you can create a logic that performs a task only when it is intersecting.
The intersection observer entry is an object with different properties.

Intersection-observer-entry logged to the console
If you log the intersection observer entry to the console from the callback, you get an object as can be seen above.
The properties of interest to us are;

  • intersectionRatio: It is the value of the target element currently intersecting with the root, as you scroll the value changes and it is a float value between 0 and 1.
  • isIntersecting: this always gives a boolean value, when it is true, it means the target element has crossed the threshold and if false it means the target element has not gotten to the threshold of the intersection or has stopped intersecting with the root element.
  • isVisible: also returns a boolean value depending on if the target element is visible to the viewport or not.

The value of “intersectionRatio” and “isIntersecting” change as you scroll up and down the viewport, that said, you can create a logic that performs a task when the “isIntersecting” property is what you need it to be or not.
To end this section, you must ensure to call the observe method on the observer object.

observer.observe(targetElement);

The observe method swings the observer into action on the target element, omitting it means the intersection observer is not yet observing the target element on the root.
Another method that can be called on the observer object is the “unobserve” method. This is called when you want the observer to stop observing the target element.

The JavaScript

Having all that information in mind you can now proceed to create an intersection of the card on the viewport.
give the intersection observer object the name cardObserver.
const cardObserver = new IntersectionObserver(cardCallback, cardOptions);

After that, create the callback and the options object. Although you can create the object first, the callback and options object should be defined above the card observer, because you cannot access a variable or an object that has not yet been initialized.



const cardOptions = {
  root: null,
  threshold: 1,
};


Enter fullscreen mode Exit fullscreen mode

The root element is set to null and recall that setting it to null means you want the root to be the viewport. The threshold is set to 1 so that the callback is fired when the card is fully in the viewport, but you can choose any value you want.



const cardCallback = function (entries) {
const [entry] = entries;
   //const entry = entries[0] 

  if (entry.isIntersecting) {
    cards.forEach((card) => card.classList.remove("show"));
    entry.target.classList.add("show");
  } else {
    entry.target.classList.remove("show");
  }
};


Enter fullscreen mode Exit fullscreen mode

The entries for this callback have just one intersection observer entry object because just one threshold was specified.
To get the element from the array you can either assign the first item to a variable which happens to be the only element or use array destructuring as shown above.

It is important to note that adding and removing the class .show to the card class list is what brings about the animation. Keeping that in mind, create a condition that checks if the “isintersecting” property of the intersection observer entry is true, it is true if the intersecting ratio is 1 and false if it is less than 1 because of the set threshold.
If "isIntersecting" is true, you add the class .show to the card class list. Before that, the class .show should be removed from every other card, because the cards are not big enough to accommodate the viewport alone and this invariably means that the show class will be applied to two cards at the same time and at every point you want just one card to be animated. When “isintersecting” becomes false, as it advances out of the viewport the else condition is then executed, which is to remove the class .show.

To end, call the observe method on all cards.



cards.forEach((card) => {
cardObserver.observe(card);
});


Enter fullscreen mode Exit fullscreen mode

Putting it all together, this is how your code should look like;



const cardCallback = function (entries) {
const [entry] = entries;

if (entry.isIntersecting) {
cards.forEach((card) => card.classList.remove("show"));
entry.target.classList.add("show");
} else {
entry.target.classList.remove("show");
}
};
const cardOptions = {
root: null,
threshold: 1,
};

const cardObserver = new IntersectionObserver(cardCallback, cardOptions);

cards.forEach((card) => {
cardObserver.observe(card);
});

Enter fullscreen mode Exit fullscreen mode




Conclusion

If you have carefully gone through the article, kudos to you. while they are many use cases of the observer intersection API, such as:

  • Implementing a sticky-menu
  • Lazy-loading of images or contents as a page is scrolled.

The focus of this article was on triggering animations using intersection observer API. The reason why intersection observer API does not impair performance is that it is asynchronous. Web APIs are asynchronous and run on a different thread on the browser, so do not slow down the execution of other tasks.

Resources

To learn more about intersection observer API and the various use cases, you can check out the following articles:

Top comments (0)