An impression in web statistics counts how many times a user views a particular element on a website. The element could be an ad, image, or something else.
Impressions are an essential piece of data that analytics software tracks.
In this article, we will learn how to use react-intersection-observer
to track image impressions and then how to store them in our browser’s localStorage.
Sandbox
The completed project is on Codesandbox. Fork it and run the code.
1<Codesandbox id="image-intersection-observer-lkhpbg" title="image-intersection-observer" />
The source code is also available on GitHub.
Prerequisites
The knowledge of Next.js and the localStorage API is required to get the most out of this article. We will also use the react-intersection-observer library to create the demo, so familiarity with it will be helpful.
Getting Started
We create a new Next.js application by running the command below in our terminal.
1npx create-next-app next-image-intersection-observer-app
Next, we navigate into the project directory.
1cd next-image-intersection-observer-app
Then run the code below to install react-intersection-observer
.
1npm install react-intersection-observer
What we are building
We are building an image tracking application. As the user scrolls through an application, we track and increase an impression count when the user scrolls past a particular image.
We will also add a visual indicator to the image to inform us when it is in view.
This demo is similar to an analytics system that tracks impressions.
Adding the Content
Update the index.js
file with the code below:
1import Image from "next/image";2export default function Home() {3 return (4 <div className="w-full h-full flex flex-col justify-center items-center px-10 pt-10 bg-zinc-900 relative">56 <h1 className="text-4xl font-bold text-white mb-16 text-center">7 Next.js Intersection Observer Article8 </h1>910 {Array.from(Array(3).keys()).map((i) => (11 <p className="text-white text-2xl mb-16" key={i}>12 Irure pariatur velit est anim ipsum anim aliquip officia velit13 consectetur. Duis sint ut consectetur ea anim. Sit proident culpa14 velit officia do incididunt Lorem in deserunt non adipisicing occaecat15 magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat16 aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt17 anim commodo amet reprehenderit.18 </p>19 ))}2021 <div className="mb-10">22 <Image23 src="/images/1.png"24 width={300}25 height={400}26 alt="something amazing"27 />28 </div>2930 {Array.from(Array(5).keys()).map((i) => (31 <p className="text-white text-2xl mb-16" key={i}>32 Irure pariatur velit est anim ipsum anim aliquip officia velit33 consectetur. Duis sint ut consectetur ea anim. Sit proident culpa34 velit officia do incididunt Lorem in deserunt non adipisicing occaecat35 magna. Occaecat occaecat esse excepteur consequat occaecat cupidatat36 aliquip labore esse ad ea. Laboris id excepteur nisi voluptate sunt37 anim commodo amet reprehenderit.38 </p>39 ))}4041 </div>42 );43}
Here, we create an array, loop through some dummy text, and add the image between the text.
Integrating react-intersection-observer
react-intersection-observer is an implementation of the Intersection Observer API for React. It informs us when an element enters or leaves the viewport.
It provides a useInView
hook we can use to monitor the inView
state of components. There is also a custom InView
component, which we will be using. We modify the index.js file to include these with:
1import Image from "next/image";2import { InView } from "react-intersection-observer";34export default function Home() {5 return (6 <div className="w-full h-full flex flex-col justify-center items-center px-10 pt-10 bg-zinc-900 relative">7 <h1 className="text-4xl font-bold text-white mb-16 text-center">8 Next.js Intersection Observer Article9 </h1>1011 {Array.from(Array(3).keys()).map((i) => (12 <p className="text-white text-2xl mb-16" key={i}>13 Irure pariatur velit est anim ipsum anim aliquip officia velit14 consectetur....15 </p>16 ))}1718 <InView threshold={0.6}>19 {({ ref, inView }) => (20 <div className="mb-10" ref={ref}>21 <Image22 src="/images/1.png"23 width={300}24 height={400}25 alt="something amazing"26 />27 <p28 className={`text-white text-2xl font-bold ${29 inView ? "text-green-600" : "text-red-600"30 }`}31 >{`Image in view? ${inView} `}</p>32 </div>33 )}34 </InView>3536 {Array.from(Array(5).keys()).map((i) => (37 <p className="text-white text-2xl mb-16" key={i}>38 Irure pariatur velit est anim ipsum anim aliquip officia velit39 consectetur....40 </p>41 ))}4243 </div>44 );45}
In the code above, we do the following:
Import
InView
fromreact-intersection-observer
.Wrap our image with
InView
using the render props method.We have access to an
inView
boolean in the render props, and we use it to add a visual indicator. The indicator informs us when the image is in view. We change the text color to green when the image is visible and red when it’s not. We also change the content of the indicator’s text based on theinView
state.
Setting up the Impression Counter
Having integrated react-intersection-observer
and can track when the image is in view, let’s set up the functionality for the impression counter. Updating the index.js file, we have:
1import { useState } from "react";2import Image from "next/image";3import { InView } from "react-intersection-observer";45function ImpressionCounter(inView, count, setCount) {6 if (inView) {7 setCount((count) => count + 1);8 localStorage.setItem("impressionCount", count);9 }10}11export default function Home() {12 const [count, setCount] = useState(0);1314 return (15 <div className="w-full h-full flex flex-col justify-center items-center px-10 pt-10 bg-zinc-900 relative">1617 <h2 className="text-4xl font-bold text-white mb-16 fixed top-10 right-10">18 Impression Count: {count}19 </h2>2021 {Array.from(Array(3).keys()).map((i) => (22 <p className="text-white text-2xl mb-16" key={i}>23 Irure pariatur velit est anim ipsum anim aliquip officia velit24 consectetur...25 </p>26 ))}2728 <InView29 threshold={0.6}30 onChange={(inView) => ImpressionCounter(inView, count, setCount)}31 >32 {({ ref, inView }) => (33 <div className="mb-10" ref={ref}>34 <Image35 src="/images/1.png"36 width={300}37 height={400}38 alt="something amazing"39 />40 <p41 className={`text-white text-2xl font-bold ${42 inView ? "text-green-600" : "text-red-600"43 }`}44 >{`Image in view? ${inView} `}</p>45 </div>46 )}47 </InView>4849 {Array.from(Array(5).keys()).map((i) => (50 <p className="text-white text-2xl mb-16" key={i}>51 Irure pariatur velit est anim ipsum anim aliquip officia velit52 consectetur...53 </p>54 ))}5556 </div>57 );58}
In the code above, we do the following:
Create a
count
state using theuseState
hook.count
will hold the impression count of the image, which we want to store. We set its default value to 0.Create an
ImpressionCounter
function. It takes ininView
,count
, andsetCount
as arguments. We check ifinView
is true, and if so, increase the count by 1 and save that new count to localStorage.To call
ImpressionCounter
, we pass it toInView
'sonChange
method.onChange
will run and callImpressionCounter
only when theinView
state changes.Display the total count in the
h2
tag so people can easily know how many impressions the image has.
Here’s what the final page looks like. Notice the increasing impression count.
Conclusion
In this article, we learned how to track image impressions using Next.js and react-intersection-observer
and store those impressions in our browser’s localStorage.
Further Reading
React Intersection Observer React Intersection Observer - Multiple Observers