Intersection Observer for Infinite Scroll in Vue Applications

Peter Eze

·

April 24, 2019

·
Guest Post

Intersection Observer for Infinite Scroll in Vue Applications

Infinite scrolling is a unique technique applied in modern web and mobile applications to fetch and display data from the server only when certain conditions are met. It gives a significant performance boost to applications since you only get to serve users a few contents on page load, and then fetching more as they scroll down the page, thereby making the application fast.

However, it appears to be one of the most dreaded features when building frontend applications. First, it is not very easy to implement as it’ll require the developer to take control of the application’s scroll events, behaviors, and properties. More importantly, it tends to affect performance severely.

In such cases, many developers run to existing libraries for help, but they are often not the best option performance-wise. Why? They use the main thread to manage the scroll events which are called frequently.

A more efficient and simpler alternative is the in-built Intersection Observer API. It provides us 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. More importantly, it provides us a callback function that will fire when the element we are observing gets into the viewport. If this doesn’t make sense yet, I promise it will, we’ll discuss it in detail shortly.

In this post, I’ll talk about the intersection and observation process and how they relate to the threshold and browser viewport. In the process, we’ll build a simple Vue application with an infinite scrolling page while fetching data with the Intersection observer. So let’s get to it!

The Intersection and observation process

Intersection in this context is nothing more than the meaning of the word. It is the point where two objects meet. In this instance, it is when a root object waits for a target object to into the root, then it invokes a callback.

photocredit to Codebeast

Consider the image above. The page is the root object, and the incoming div-as-target rectangle is the target object. The root observes the target like a hunter waiting for its prey, the moment the target enters into the root, it fires a callback function. In the image on the right, another element div-as-root can be the root. It performs exactly the same functions as the page in the left image.

Create an Intersection Observer

To create an Intersection observer, we only need two things, an options object that let you control the circumstances under which the observer’s callback is invoked, and the callback function that gets fired when the target is in view. Here’s how:

const options = {
  root: null, /* uses the page as root */
  threshold: 0 
};
    
const observer = new IntersectionObserver(callback, options);

The options object has two major fields, the root, and the threshold.

root

The element that is used as the viewport for checking the visibility of the target must be the ancestor of the target. It defaults to the browser viewport if not specified or if null. If you want to select a particular element as root, here’s how:

const options = {
  root: document.querySelector('#divAsRoot'), /* uses the Div element as root */
};

threshold

The threshold indicates at what percentage of the target’s visibility the observer’s callback should be executed.

photocredit to Codebeast

You may want the observer to only execute the callback when the target gets halfway into view (50%), completely in view (100%) or the second it gets into view (0%). The threshold is how you decide. It defaults to 0 meaning that the observer will execute the callback when even one pixel is in view. A value of 1.0 means that the callback won’t run until every pixel is in view. The same goes for 0.5. The callback fires only when the target is halfway into view.

Observing the target

Having created the observer, we need to pass it a target to observe. The target could be any element with an identifier.

const target = document.querySelector('#divAsTarget');
observer.observe(target);

Having understood how intersection and observation work, let’s put this knowledge to good use and implement it in a Vue application.

What should we build?

Let’s build a page that fetches random users from the Random Users API. As we scroll down the page looking at the users, it fetches even more users, hence an infinite scrolling page. We’ll use the Intersection observer to determine when the user has scrolled down to the bottom of the page ( a target comes to view), and we fetch more users in the callback function.

Create a Vue project

Create a Vue project and install Axios as a dependency for making our API calls. Better still, run the commands below if you have the Vue CLI tool installed.

vue create scrolling-demo
cd scrolling-demo
npm install --save axios
npm run serve

Having created your project, the first thing we want to do is fetch the initial users and display them on the page. Like earlier said, we’ll fetch the users from the Random User API. It’s an API for generating random users, with details like the user’s name, image, location, etc. For our use case, we are only interested in the user’s image and name.

<!-- src/App.vue --> 
    
<script>
import axios from "axios";
export default {
  name: "app",
  data() {
    return {
      users: [],
    };
  },
  methods: {
    fetchUsers() {
      for (var i = 0; i < 5; i++) {
        axios.get(`https://randomuser.me/api/`).then(response => {
          this.users.push(response.data.results[0]);
          console.log(response)
        });
      }
    },
    beforeMount() {
      this.fetchUsers();
    }
  }
}
</script>
  • fetchUsers() - Fetches five initial users from the Random User API using Axios. Then updates the users array we defined in the data() function with the fetched users.
  • beforeMount() - calls the fetchUsers() function.

If you open your browser console, you should see an array of five users logged.

If you’re wondering why we had to make five calls, it is because the Random User API only returns one random user at a time. So in order to get five initial users, we had to make five calls.

Let’s update the user interface with the users we’ve fetched. We just have to update the template section with code below:

<!-- src/App.vue --> 
<template>
  <div id="app">
    <div class="user" :key="user.id.value" v-for="user in users">
      <div>
        <img class="img" :src="user.picture.large">
      </div>
      <div>
        <p>{{ user.name.first }} {{ user.name.last }}</p>
        <ul></ul>
      </div>
    </div>
    <div>
      <span ref="target"/>
    </div>
  </div>
</template>

Here, we are using the v-for directive to render a list of our users based on the users array. If we save and run the app, we should now see our initial five users on the browser

I’ve added some custom styling to get the visual output above, so it’s not a problem if yours don’t look exactly like mine.

You may have noticed that nothing happens when we get to the bottom of the page right?, well that’s because we only loaded five users and nothing more. Now let’s add our Intersection observer.

To create the observer, we define the options object and set the observer to null in the data() function

<!-- src/App.vue --> 
    
const options = {
  root: null,
  threshold: 0
};
export default {
  data() {
    return {
      users: [],
      observer: null
    };
  }
}   

Then we need to create a mounted() lifecycle method and define our observer therein.

mounted() {
this.observer = new IntersectionObserver(this.callback, options);
}

At this point, we should probably give the observer a target to observe. So we’ll create a div element below our initial users list such that when we scroll past all the existing users on the page and get to that target div element, the callback function will be fired. Update the template section with the new element

  <div ref="divAsTarget"><h3>Loading ... </h3></div> /* below the user's list */

Now that we have a target, let’s give our observer something to observe.

mounted() {
this.observer = new IntersectionObserver(this.callback, options);
this.observer.observe(this.$refs.divAsTarget);
}

We passed in a callback function as the first parameter when creating our observer above, let’s define that function in our components methods object.

callback(entries, observer) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
       this.fetchUsers();
    }
  });
}

When the callback function fires, we call the fetchUsers() function to add five more users to our users array and that will update the list with more users. This way, each time we scroll to the bottom of the page, the target element comes into view and our observer fires the callback which will then update our list of random users with more users. Let’s check the app again.

Thus infinite scrolling with Intersection observer.

Final thoughts

We have gone over the basics of Intersection and observation. I want to believe we’ve understood how this helps us build better apps with the need for this unique feature. If so, go ahead and build something cool with it. I will also like to mention that this post was inspired by this earlier post written by Codebeast on Intersection observers in React. You should probably check it out if you’re curious about how to do this in React.


Peter Eze

Written by Peter Eze.
Follow on Twitter

© 2019, Codebeast.dev