Infinite scrolling is a feature on websites and applications where a user scrolls down and reaches the bottom of the current page of content and then the next page of content is loaded and displayed. This effect replaces clicking through pagination navigation. In situations with mobile devices and touchscreens, infinite scrolling may present a better user experience.
This feature is especially useful when you need to load large amounts of data or images when the user needs them rather than at all once. Social media outlets like Twitter, Facebook, and Instagram have popularized this over the years.
In this tutorial, you will build an example Vue.js application that uses infinite scrolling for fetching and displaying data from the Random User API.
To complete this tutorial, you will need:
This tutorial was verified with Node v15.3.0, npm
v6.14.9, vue
v2.6.11, and axios
v0.21.0. This tutorial was edited to reflect changes in migrating from earlier versions of @vue/cli
.
For the purpose of this tutorial, you will build from a default Vue project generated with @vue/cli
.
- npx @vue/cli create vue-infinite-scrolling-example --default
This will configure a new Vue project with default configurations: Vue 2
, babel
, eslint
.
Navigate to the newly created project directory:
- cd vue-infinite-scrolling-example
Next, install axios
:
- npm install axios@0.21.0
At this point, you should have a new Vue project with Axios support.
There are various npm packages for an infinite scroll that you can use for your Vue app. But some of these may be overkill for your needs due to features you do not require or a large number of dependencies.
For the purposes of this tutorial, you will build a JavaScript function that fetches a new set of data when scrolled to the bottom of the browser window. This will not require additional plugins or packages.
First, open App.vue
in your code editor.
Next, replace the code in the template
with a display that loops over an array of users
to display a picture, first name, last name, date of birth, city, and state:
<template>
<div id="app">
<h1>Random User</h1>
<div
class="user"
v-for="user in users"
:key="user.first"
>
<div class="user-avatar">
<img :src="user.picture.large" />
</div>
<div class="user-details">
<h2 class="user-name">
{{ user.name.first }}
{{ user.name.last }}
</h2>
<ul>
<li><strong>Birthday:</strong> {{ formatDate(user.dob.date) }}</li>
<li><strong>Location:</strong> {{ user.location.city }}, {{ user.location.state }}</li>
</ul>
</div>
</div>
</div>
</template>
Then, replace the the code in the <style>
with CSS rules for arranging the display of each user:
<style>
.user {
display: flex;
background: #ccc;
border-radius: 1em;
margin: 1em auto;
}
.user-avatar {
padding: 1em;
}
.user-avatar img {
display: block;
width: 100%;
min-width: 64px;
height: auto;
border-radius: 50%;
}
.user-details {
padding: 1em;
}
.user-name {
margin: 0;
padding: 0;
font-size: 2rem;
font-weight: 900;
}
</style>
Next, replace the code in the <script>
with code that makes an initial request of five users to the API and is called during the beforeMount
lifecycle:
<script>
import axios from "axios";
export default {
data() {
return {
users: [],
};
},
methods: {
getInitialUsers() {
axios.get(`https://randomuser.me/api/?results=5`).then((response) => {
this.users = response.data.results;
});
},
},
beforeMount() {
this.getInitialUsers();
},
};
</script>
The date of birth (DOB) is provided as a string in ISO 8601 format. In order to make this more human-readable, you can convert the string into a date string using Date.prototype.toDateString()
:
<script>
// ...
export default {
// ...
methods: {
formatDate(dateString) {
let convertedDate = new Date(dateString);
return convertedDate.toDateString();
}
// ...
},
// ...
};
</script>
This initial request will display five users when a user opens the application.
Note: Previously, this tutorial performed multiple requests to the Random User API to initially load more than the single user result. This section has been rewritten to use the new results
parameter that is provided by the API.
In your terminal window, compile and serve the application:
- npm run serve
After opening the application in your web browser, there will be five random users displayed.
The infinite scroll logic will require detecting when the user has reached the bottom of the window. This can be accomplished with the following three properties:
document.documentElement.offsetHeight
: the amount of pixels for the entire height of the document element.document.documentElement.scrollTop
: the current amount of pixels positioned from the top of the document element.window.innerHeight
: the number of pixels for the height of the screen.When document.documentElement.scrollTop
plus window.innerHeight
are equal to document.documentElement.offsetHeight
, it can be assumed that the user has reached the bottom of the window.
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
if (bottomOfWindow) {
// ...
}
};
Here, window.onscroll
listens for the scroll
event and will perform the check when the event is detected.
Note: When binding events, especially to scroll events, it is good practice to debounce the events. Debouncing is when you only run a function once a specified amount of time has passed since it was last called.
However, for the needs of this tutorial, a debouncer will not be applied.
Inside of the if
condition, let’s add a GET
service method with Axios to fetch another random user from the Random User API:
axios.get(`https://randomuser.me/api/`).then(response => {
this.users.push(response.data.results[0]);
});
Now, revisit App.vue
in your text editor and add your new code.
In your component’s methods
, you will need to create a new function called, getNextUser()
and have that loaded in the mounted()
lifecycle method.
<script>
import axios from "axios";
export default {
data() {
return {
users: [],
};
},
methods: {
formatDate(dateString) {
let convertedDate = new Date(dateString);
return convertedDate.toDateString();
},
getInitialUsers() {
axios.get(`https://randomuser.me/api/?results=5`).then((response) => {
this.users = response.data.results;
});
},
getNextUser() {
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
if (bottomOfWindow) {
axios.get(`https://randomuser.me/api/`).then(response => {
this.users.push(response.data.results[0]);
});
}
}
}
},
beforeMount() {
this.getInitialUsers();
},
mounted() {
this.getNextUser();
}
}
</script>
Now, recompile and serve your application.
After opening the application in your web browser and scrolling to the bottom of the page, a new user is added to the page.
With each scroll to the bottom of the page, you fetch new data with Axios then push that data to an array.
In this tutorial, you built an implementation of an infinite scroll in a Vue.js application. It relied upon beforeMount
and mounted
lifecycle hooks to initialize and prefetch the requests to an API.
To lazy load images, push an image source to a data array, iterate through it in your template
and bind your <img :src="">
to the array.
If you’d like to learn more about Vue.js, check out our Vue.js topic page for exercises and programming projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
What is an easy way to debounce? I was using Lodash before. Anyway without the dependency?
Terrific article Dave. Thank you!
Your example was working great, but at one point, my code changed enough (conditional v-if divs) and the scroll stopped working. Specifically, an incorrect document.documentElement.offsetHeight was being captured during the mounted() hook.
I couldn’t pin-down what caused the document.documentElement.offsetHeight property value to store a number that no longer represented the bottom of the scroll.
To solve the problem, I changed the condition to:
let bottomOfWindow = document.documentElement.scrollHeight - document.documentElement.scrollTop === document.documentElement.clientHeight;
It’s working again, and wanted to share with anyone running into similar issues.
Thanks this is very useful it has helped me alot,
Awesome tutorial, thanks for sharing, Dave! I just wanted to chime in and let you (and your readers) know that with the latest version of the Random User API, there’s no need to make multiple API calls anymore, in order to retrieve more than 1 record at a time. You’d simply add a “results” parameter along with the desired value for it, e.g. https://randomuser.me/api/?results=5000
Hope this helps :)