Building a Lazy Loader from Scratch in React (Part 3)
Tags: javascript, react, webdev, frontend
Published: 2023-11-30 15:00:32

## Table of Contents<a name=""></a>

- [Introduction](#Introduction)
- [Understanding the effect](#Understanding_the_effect)
- [The loading indicator](#The_loading_indicator)
- [Added a loaded state](#Added_a_loaded_state)
- [Implementing the effect](#Implementing_the_effect)
- [The final lazy loader](#The_final_lazy_loader)


-----

## Introduction<a name="Introduction"></a><a name="Introduction"></a>

Finally, it's time for the third and last installment of this series of how to build a lazy loader from scratch using [React](https://www.codeguage.com/courses/react/) and the [`IntersectionObserver` API](https://www.codeguage.com/courses/advanced-js/intersection-observer-introduction).

Here's a quick recap of the last two installments:

In the [first part](https://www.codeguage.com/blog/lazy-loader-react-1), we saw what exactly is a lazy loader; how to bring React and the `IntersectionObserver` API together in powering a very simple, minimal lazy loader; and finally implemented the lazy loader using a multitude of ideas from React and the observer API

Then in the [second part](https://www.codeguage.com/blog/lazy-loader-react-2), we solved a couple of issues with this minimal lazy loader, such as that of cumulative layout shift (CLS) and not being responsive (i.e. the image was overflowing the viewport on smaller devices). In the end, we obtained a fully-functional, responsive and free-from-CLS-issues lazy loader, after quite a long fruitful discussion.

Now, in this last part, we shall work on building perhaps the coolest of all features — an elegant, **fade-in effect** to introduce our lazy image to the world.

_Ok, well that got way too dramatic!_

In this chapter, we'll see how to implement a fade-in transition as a lazy image loads into view. We'll be using knowledge from [CSS](https://www.codeguage.com/courses/css/), in particular from the topics of [CSS transitions](https://www.codeguage.com/courses/css/transitions-introduction), [`opacity`](https://www.codeguage.com/courses/css/color-alpha-and-opacity), and some other ad-hoc properties.

Without any further ado, let's get started.

-----

## Understanding the effect<a name="Understanding_the_effect"></a><a name="Understanding_the_effect"></a>

Before we begin, let's spare some time to understand exactly what we're trying to achieve over here.

Go on, open the link below, and try to exhibit the best of your analysis skills to infer what is happening as the lazy image on the page loads into view.

[Live Example](https://www.codeguage.com/courses/react/examples/blog-lazy-loader-7)

Supposing your analysis is complete and that you're now ready to cross-check it, let's together see what's happening in the page above.

As soon as the image successfully loads, it doesn't snap into view like what we've been seeing thus far in this series. Instead, the image _smoothly_ appears into view.

Moreover, another thing worth noting in the example above is that while the image is still in the process of loading, there is an animation going on in the background of the container for...

_What do you think, why is the animation happening?_

_Yes, you guessed it right!_ The animation is happening in order to signal the loading of the underlying image.

So in deconstructing the given example, we see that there are two things that we ought to accomplish in the following discussion:

- One is to implement the fade-in transition itself.
- The other is to implement some kind of a loading indicator (it could be an animating background, a gif, an HTML-powered element, anything).


Alright, with this high-level overview of the effect and its prerequisites, it's time to get into it and start building it. We'll start with the loading indicator.

-----

## The loading indicator<a name="The_loading_indicator"></a><a name="The_loading_indicator"></a>

While a lazy image loads, after its scrolled into the viewport, we must use _something_ to clearly indicate to the user that the image is actually being loaded and that the application is not stuck at a blank view.

It's much better to show some progress in action rather than showing nothing and assuming that the user will be able to infer on his/her own that something is about to show in the blank gray container.

No, don't expect your user to infer things.

_You_ have to tell them about it, in this case, tell them that the image is loading in the background. This leads to a better UX (user experience).

Now for the loading indicator, we have a handful of choices:

- Use a generic loading gif, like the spinner icon.
- Use a branded gif that aligns with our brand icon.
- Use an HTML element along with some CSS.
- Use some kind of CSS ingenuity in the background.


We'll be choosing the last one because we feel that it's really easy to implement it and also it arguably gives the most modern vibe these days in React apps (in fact, in all kinds of apps).

You're obviously free to choose anything you like.

The ingenuity we're talking about here is a **_shimmer effect_**. We've already covered how to create a shimmer loading animation in the following article:

[How to create a shimmer effect using HTML and CSS?](https://www.codeguage.com/blog/shimmer-effect-html-css)

Go through the article and see how to create a very popular effect in today's modern application world. It's your time to shine through. (_Or should we say, shimmer through?_)

Here's our CSS code for `.lazy`, since it's the element with the background:

```css
.lazy {
   position: relative;
   background: linear-gradient(-45deg, #f3f3f3 40%, #fafafa 50%, #f3f3f3 60%);
   background-position-x: -50%;
   background-size: 300%;
   animation: shimmer 1.5s linear infinite;
}

@keyframes shimmer {
   to {
      background-position-x: -350%;
   }
}
```

Obviously, we can only see `.lazy` when `.lazy_img` is hidden. So, let's go on and hide `.lazy_img` using `display: none` (we'll be changing this shortly below):

```css
.lazy_img {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   display: none;
}
```

And here's our result:

[Live Example](https://www.codeguage.com/courses/react/examples/blog-lazy-loader-7)

Obviously, there's a lot to rectify in here, for the implementation is far from perfect. And that's what we'll be doing in the next section.

-----

## Added a loaded state<a name="Added_a_loaded_state"></a><a name="Added_a_loaded_state"></a>

Notice the animation created in the previous section. Yes it's great, elegant, amazing, spectacular, and whatnot, but all this sounds good only as long as we're giving a concern to efficiency.

When the lazy image loads into view, supposing that we display the image in `.lazy` (by removing `display: none`), there's absolutely no point in keeping the animation running; the background gets hidden and so we should also stop the animation then and there.

And for this, we obviously need an HTML class that could be used in the CSS to contain the styles when the lazy image gets loaded successfully.

Let's call it **`.lazy--loaded`** (following the same slightly modified BEM naming convention that we've used in the preceding chapters).

_But where will this class come from?_ Well, it'll come from our React app, exactly when the lazy image gets loaded.

In the following code, we extend our `LazyImage` component with another state value, called **`loaded`**, to represent whether the underlying `<img>` has successfully loaded or not.

```jsx
function LazyImage({ src, width, height }) {
   const [srcExists, setSrcExists] = useState(false);
   const [loaded, setLoaded] = useState(false);
   const divRef = useRef();

   useEffect(() => {
      divRef.current.setSrcExists = setSrcExists;
      LazyImage.io.observe(divRef.current);
   }, []);

   function onLoad() {
      setLoaded(true);
   }

   return (
      <div style={{ maxWidth: width }}>
         <div
            style={{ paddingBottom: `${height / width * 100}%` }}
            ref={divRef}
            className={'lazy' + (loaded ? ' lazy--loaded' : '')}
         >
            <img onLoad={onLoad} className="lazy_img" src={srcExists ? src : null} />
         </div>
      </div>
   );
}
```

Initially, `loaded` is `false` and rightly so — the image doesn't get loaded to begin with. `loaded` is transitioned to `true` inside the **`onLoad()`** event handler of the `<img>` element, which fires when the image successfully loads.

Observe the application of the `.lazy--loaded` class in the code, in line 20. Only when `loaded` is `true` is this class added to the `.lazy` element.

With the class being created at the right time, let's turn to the CSS and remove the `animation` style in `.lazy--loaded`.

```css
.lazy--loaded {
   animation: none;
}
```

Perfect!

We're now just left with one last thing and that is implementing the fade-in effect on the `<img>`. Fortunately, using precisely three properties, it's really quick to do so.

Let's move on.

-----

## Implementing the effect<a name="Implementing_the_effect"></a><a name="Implementing_the_effect"></a>

To implement any kind of a fade effect on any element, we need essentially three things, or better to say, three CSS properties: `opacity`, `visibility`, and `transition`.

Let's see what each of these does.

- `opacity` literally controls the opacity, i.e. the fade amount, of the element. A completely faded-in element would have an `opacity` of `1` (fully opaque) while a completely faded-out element would have an `opacity` of `0` (completely transparent).
- `visibility` controls the visibility of the element. This is important so that when the element is completely faded-out, it's not there on the webpage. (`opacity: 0` just makes the element transparent, not actually hidden from active view, thus, we can interact with everything inside the element)
- `transition` is the thing that allows us to _'smoothly'_ transition from a faded-in state to a faded-out state, or vice versa.


_And that's pretty much it._

In our lazy loader, all what we need to do now has to be done inside the CSS; we're free from our JSX code.

_The good, old CSS._

First things first, we'll make the `<img>` initially completely faded-out along with the application of `transition`, and also remove the previous application of `display: none`:

```css
.lazy_img {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   visibility: hidden;
   opacity: 0;
   transition: 0.6s ease-out;
}
```

This faded-out effect needs to be removed (thus giving a fade-in effect) exactly when the `<img>` successfully loads. And that's when the `.lazy--loaded` class is set on the `.lazy` element.

This leads us to the following code:

```css
.lazy--loaded .lazy_img {
   visibility: visible;
   opacity: 1;
}
```

`opacity` is set to `1` while `visibility` is set to `visible` when the `<img>` has been loaded. _Simple._

And that completes our implementation of the fade-in effect. Time to test it now.

-----

## The final lazy loader<a name="The_final_lazy_loader"></a><a name="The_final_lazy_loader"></a>

Recall the following code in `App.jsx` that we use to test our lazy images:

```jsx
import LazyImage from './LazyImage';

function App() {
   return (
      <>
         <h1>Demonstrating lazy loading (with a fade-in effect)</h1>
         <p style={{ marginBottom: 1000 }}>Slowly scroll down until you bring the lazy image into view.</p>
         <LazyImage width={640} height={424} src="/image.jpg" />
      </>
   );
}

export default App;
```

Everything is the same as left in the previous part of this series, except for the heading. We don't need to add/remove anything since nothing has changed from the perspective of the consumption of `LazyImage`.

Here's the link to the page:

[Live Example](https://www.codeguage.com/courses/react/examples/blog-lazy-loader-8)

Go on and scroll down and wait for 2 seconds before you finally see the lazy image putting aside its laziness.

_And that's essentially it._

Congratulations! We've completed our series of implementing a lazy loader from scratch.

Just one last thing before we conclude.

In all the examples we've seen thus far, we've been using `setTimeout()` to manually delay the time until an image is requested for and ultimately loaded. Now that we've completely tested everything in the lazy loader, it's the high time to let go of this `setTimeout()` call.

In the code below, we do so:

```js
function ioCallback(entries, io) {
   entries.forEach(entry => {
      if (entry.intersectionRatio >= 0 && entry.isIntersecting) {
         io.unobserve(entry.target);
         entry.target.setSrcExists(true);
      }
   });
}
```

_Amazing._

-----

## More from Codeguage

In the meanwhile, you can consider going through some of the following articles published by us on Dev 👇:

{% embed https://dev.to/codeguage/is-frontend-development-easier-than-backend-h77 %}

{% embed https://dev.to/codeguage/how-to-set-up-rollup-for-a-react-application-2322 %}

{% embed https://dev.to/codeguage/4-incredible-vs-code-features-that-you-cant-miss-1nf4 %}