Infinite scroll images with Intersection Observer

Moronfolu Olufunke

Building web applications involves displaying text, videos, and images to users. Sometimes, the sheer size of these assets could cause application lag; therefore, the need to display these assets in bits to users.

Developers have devised several means of compressing images, and notable among these are applying pagination to make users' experience better. The use of an infinite scroll has proven to provide a better user experience. Building an infinite scroll into projects has gotten even easier with the introduction of the Intersection Observer API in browsers.

This tutorial will walk you through implementing infinite scroll in an image gallery using the intersection observer web API.

Sandbox

The completed project can be found on CodeSandbox. Fork it and run the code.

Github

Check out the complete source code in this Github repository.

What really is Infinite Scroll?

Infinite scroll is an interactive feature that automatically loads more content as the user scrolls through the page - meaning it creates a seemingly endless page.

The interactivity has successfully eliminated the friction of pagination and led to increasing engagement - little wonder you’d see it on almost all social media platforms and E-commerce websites.

“Just a few more shoes to check….” But you never get to the end of the seemingly endless list of shoes before you doze right off!

This tutorial will leverage the Intersection Observer API to achieve infinite scroll.

Intersection Observer API

The Intersection Observer API is a browser API that allows you to track a DOM element's location and visibility relative to a specified defined root element before calling a callback function if certain circumstances are satisfied.

What is the root element?

While monitoring the visibility of the target DOM element, the root element is the ancestor of the target element that acts as the viewport for checking the element's visibility.

This root element will automatically traverse up the DOM tree to the browser's viewport if no target element is specified.

There are several use cases for the Intersection Observer API, but the focus will be on implementing the infinite scroll.

Prerequisites

This tutorial assumes the reader has the following:

  • Node.js installed on their computer
  • Basic knowledge of JavaScript
  • Familiarity with Nuxt.js
  • Tachyons CSS Toolkit
  • Unsplash account

Dependencies Installation

Creating a Nuxt.js Project

Open a terminal on your computer and run the following command.

1npm init nuxt-app intersection-observer
2cd intersection-observer
3npm run dev

Running npm run dev starts the project on the development server at localhost:3000 in your browser.

Creating the Skeleton UI with static images

Before wading through an endless list of images, you’ll build a skeleton interface to lay the foundation of your gallery.

Creating the gallery component

The grid of images across each row shows a photo gallery. Navigate to the components folder, create a Gallery.vue file and add the code block below.

1<template>
2 <div class="pv4 ph3 bg-washed-yellow h-100 mv3 mh2 br2">
3 <div class="flex flex-wrap justify-between-l justify-center">
4 <a class="db center mw5 tc black link dim" title="Frank Ocean's Blonde on Apple Music" href="https://geo.itunes.apple.com/us/album/blonde/id1146195596?at=1l3vqFJ&mt=1&app=music">
5 <img class="db ba b--black-10" alt="Frank Ocean Blonde Album Cover" src="https://s3-us-west-1.amazonaws.com/tachyonsio/img/Blonde-Frank_Ocean.jpeg">
6 <dl class="mt2 f6 lh-copy">
7 <dt class="clip">Title</dt>
8 <dd class="ml0">Blonde</dd>
9 <dt class="clip">Artist</dt>
10 <dd class="ml0 gray">Frank Ocean</dd>
11 </dl>
12 </a>
13 </div>
14 </div>
15</template>

Proceed to the index.vue file in the pages folder and import the Gallery component below the GalleryHeader component. At this point, your pages/index.vue should look like this:

1<template>
2 <div>
3 <GalleryHeader />
4 <Gallery />
5 </div>
6</template>

Your webpage should then look like this:

Adding Dynamic Images

After setting up the basic UI skeleton for the page, the next thing to be done is to add dynamic images that populate automatically from an API. This article will be using the unsplash API. This tutorial focuses more on using the Intersection Observer API for infinite scroll. Here is a comprehensive guide written by the Unsplash team to guide you on setting up an Unsplash account up.

At the end of this setup, your components/gallery.vue should look like the below:

1<template>
2 <!-- repeated code removed -->
3 <a v-for="image in imageList" :key="image.id" class="w-100 w-40-m w-30-l db center tc black link dim ma2" href="">
4 <img class="db center ba b--black-10 ma2" :src="image.urls.small" :alt="image.alt_description" >
5 <dl class="mt2 f6 lh-copy">
6 <dt class="clip">Artist</dt>
7 <dd class="ml0 gray">{{image.user.name}}</dd>
8 </dl>
9 </a>
10</template>
11<script>
12 import axios from "axios"
13 export default {
14 data: () => ({
15 imageList: [],
16 page: 1
17 }),
18 mounted(){
19 this.imageIntersected()
20 },
21 methods: {
22 imageIntersected() {
23 axios
24 .get(
25 `https://api.unsplash.com/photos?page=${this.page}&per_page=21&w=1280&h=1280`,
26 {
27 headers: {
28 Authorization:
29 "Client-ID {add your client-ID here}",
30 "Accept-Version": "v1"
31 }
32 }
33 )
34 .then(response => {
35 this.page++;
36 const lists = response.data;
37 this.imageList = [...this.imageList, ...lists];
38 })
39 .catch(() => {
40 this.imageList = [];
41 });
42 }
43 }
44 }
45</script>

The imageIntersected method gets dynamic images from the unsplash API in the code above. The imageList array displayed in the v-for gets populated with the data obtained when this method is mounted.

Instead of Unsplash, you could use data of images stored locally or retrieved from an API.

Your UI, in return, should look like this:

Using the Intersection Observer API in your code involves several things - but foremost is using the object constructor function to create a new instance of the Intersection Observer like so:

1new IntersectionObserver()
2
3const observer = new IntersectionObserver(callbackFunction, options);
4const targetElement = document.querySelector(".observer");
5
6observer.observe(targetElement);

The IntersectionObserver receives two arguments - the first is the callback function and the second is the options.

  • Callback function: The intersection of targeted elements with viewport execute the call back function.
  • Options: The options argument is optional, but its added function controls how the observer function executes.

The options argument is an object and can have fields like: root, threshold and rootMargin.

1const options = {
2 root: null,
3 rootMargin: "50px",
4 threshold: 1.0
5};
6observer.observe(targetElement, options);

Creating an observer component

With the basics of using the Intersection Observer API in your code being covered, get started with integrating it in your photo gallery project. Create a components/Observer.vue file and add the code below:

1<template>
2 <div class="observer"/>
3</template>
4<script>
5 export default {
6 props: {
7 options: {
8 type: Object
9 }
10 },
11 data: () => ({
12 observer: null,
13 }),
14 mounted() {
15 const observerOptions = this.options || {};
16 this.observer = new IntersectionObserver(([entry]) => {
17 if (entry && entry.isIntersecting) {
18 this.$emit("intersect");
19 }
20 }, observerOptions);
21 this.observer.observe(this.$el);
22 },
23 destroyed() {
24 this.observer.disconnect();
25 },
26 };
27</script>

Linking the Observer Component to the Gallery Component

Register the Observer.vue component in the Gallery.vue component to get the expected result. At this point, Gallery.vue should look like this:

1<template>
2 <div class="pv4 ph3 bg-washed-yellow h-100 mv3 mh2 br2">
3 <div class="flex flex-wrap justify-between-l justify-center">
4 <a
5 v-for="image in imageList"
6 :key="image.id"
7 class="w-100 w-40-m w-30-l db center tc black link dim ma2"
8 href="#"
9 >
10 <img
11 class="db center ba b--black-10 ma2"
12 :src="image.urls.small"
13 :alt="image.alt_description"
14 />
15 <dl class="mt2 f6 lh-copy">
16 <dt class="clip">Artist</dt>
17 <dd class="ml0 gray">{{ image.user.name }}</dd>
18 </dl>
19 </a>
20 <Observer @intersect="imageIntersected" />
21 </div>
22 </div>
23</template>
24<script>
25 import axios from "axios";
26 import Observer from "@/components/Observer";
27 export default {
28 components: {
29 Observer,
30 },
31 data: () => ({
32 imageList: [],
33 page: 1,
34 }),
35 methods: {
36 imageIntersected() {
37 axios
38 .get(
39 `https://api.unsplash.com/photos?page=${this.page}&per_page=10&w=1280&h=1280`,
40 {
41 headers: {
42 Authorization: `Client-ID {add your unsplash Client Id here}`,
43 "Accept-Version": "v1",
44 },
45 }
46 )
47 .then((response) => {
48 this.page++;
49 const lists = response.data;
50 this.imageList = [...this.imageList, ...lists];
51 })
52 .catch(() => {
53 this.imageList = [];
54 });
55 },
56 },
57 };
58</script>

At this point, more images should automatically load as you navigate through the application in your browser.

Conclusion

This post has gone through building a photo gallery that has infinite scroll implemented using the Intersection Observer and Unsplash APIs.

The resources below will be helpful for further studies:

Moronfolu Olufunke

Frontend Engineer | Tecnical Writer | Story Teller

Olufunke is a Frontend Engineer and technical writer who enjoys building stuff and writing about them.