Enhancing UX through Image Loading in React.JS
Learn how to conditionally display a spinner while waiting for targeted images to load
Things can quickly go from "I am super excited to view this landing page" to "Why is it taking so long for this image to show? This is frustrating!". Sadly, end users fail to understand that this isn't entirely on you - the dev, but may be due to the client's poor internet connection.
In this tutorial, we'll learn how to tone down the bad UX by rendering the page contents after loading an image resource. We can wait for the entire image resource in a webpage to load, but this may take more time and add to the frustration. Hence this article focuses on listening for a specific image to load, in our case, the hero image.
Prerequisites
Code editor and browser.
Basic knowledge of JavaScript events and Event Handlers.
Basic knowledge of React and React Hooks.
Basic knowledge of Git, and package managers (npm or yarn).
Let's get started
Installation
Cloning the Starter App
To get started, I have provided a starter app on GitHub. To help us focus on implementation, the UI has been built already. The application has been configured with Create React App
and SCSS
. Open your terminal and run the following command:
git clone -b starter https://github.com/utin-francis-peter/img-loading-tutorial.git && cd img-loading-tutorial
The above command will clone the starter branch of the repo into your local machine and make the project folder your current working directory.
Next, run the following command in your terminal to install the dependencies.
yarn
Or if you prefer npm, use:
npm install
Installing React Spinners
Next, let's install React Spinners
, which would be conditionally displayed while waiting for our image to finish loading. Use the following command to install it:
yarn add react-spinners
or
npm install react-spinners
Once you have installed React Spinners
, there are no further dependencies to install.
To start the application, run:
yarn dev
or
npm dev
Voilà! You should see the hero page displayed on your browser as seen below:
We'll learn to display the page content after fully loading the hero image. Let's dive!
Implementing the Logic
Open the App.jsx
file and paste the following codes just before the return
statement:
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (document.readyState === "complete") {
setIsLoaded(true);
} else {
if (typeof window !== "undefined") {
window.addEventListener("load", () => {
const heroImg = document.getElementsByTagName("img")[0];
if (heroImg.complete && heroImg.naturalHeight !== 0)
setIsLoaded(true);
});
}
}
});
In the above snippet, we did the following:
Created an
isLoaded
state that tells if the target image is loaded.Checked the status of our document. The document status can be either loading, interactive, or complete.
complete
indicates that all resources are fully loaded including the image(s). If this is true, toggleisLoaded
state totrue
. Else we listen for aload
event on the window. The subtle difference betweendocument.readyState
and theload
event is that the former will fire as soon as the document is ready, even if some resources such as images are still loading. The latter will only fire when the entire page has finished loading, including image resources.Using
document.getElementsByTagName("img")[0]
, we received anHTMLCollection
, whose data structure is similar to an array. Afterward, we accessed its first element at position 0, which is the first image we have on the webpage (the hero image).Accessing a single image element then gives us access to the
complete
andnaturalHeight
properties that can be used to know if an image is fully loaded.
Conditionally Displaying Spinner
A spinner in React is a small visual element that shows up on a web page when something is loading or processing. It looks like a little circle that spins around and around. You might have seen it before when you're waiting for a video to load or for a game to start.
Since we now have a state that only toggles to true
when page resources or target image is fully loaded, let's see how we can conditionally display a spinner that disappears after the image is loaded.
First, import ClipLoader
component from React Spinners
into the App.jsx
file:
import ClipLoader from "react-spinners/ClipLoader";
Next, replace your existing JSX in the app.jsx file with the codes below:
<>
<div className="spinner" style={{ display: isLoaded ? "none" : "flex" }}>
<ClipLoader size={70} loading={true} color="#f6921e" />
</div>
<div className="app" style={{ display: isLoaded ? "block" : "none" }}>
<header className="container">
<h3>logo</h3>
<nav>
<ul>
<li>
<a href="/">nav</a>
</li>
</ul>
</nav>
</header>
<main>
<section className="hero container">
<div className="hero-desc-container">
<p>With the right mind, you will</p>
<h1 className="fs-lg">SUCCEED</h1>
<p className="hero-desc">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet
iusto assumenda corporis reprehenderit maxime non veniam.
</p>
<button className="btn-lg">BELIEVE IN YOU</button>
</div>
<div className="hero-img">
<img
src="https://media.istockphoto.com/id/1445687851/photo/portrait-of-an-adult-siblings.jpg?s=612x612&w=0&k=20&c=CtHQM49ho0qkKjIgRk9XwJJlvwSsXm_XTJ2xm6p7nKI="
alt="img from unsplash"
/>
</div>
</section>
</main>
</div>
</>
From the snippets above, we created a second div
that's used to house our ClipLoader
component. The added inline styles are used to conditionally display or hide either of the divs
depending on the status of isLoaded
application state. The idea is to have the page resources loading in the background while displaying the spinner.
Conclusion
Images play a vital role in enhancing the visual appeal of web pages. However, it's important to optimize image loading to improve user experience. Techniques such as lazy-loading and resizing can help reduce file size and improve load times. Additionally, providing users with a loading spinner can enhance engagement and a good user experience.
A similar principle can also be applied when making API calls. A spinner gets conditionally rendered while awaiting the arrival of requested data.
You can find the finished project on GitHub for reference and further exploration.
That's all, folks! I hope this was helpful. Please do well to leave some feedback, share the article with friends, and connect with me on LinkedIn.
Cover image by vectorjuice on Freepik.