Make a lazy-loading div Background Image in React (Typescript) without any third party tools

Make a lazy-loading div Background Image in React (Typescript) without any third party tools

We all know that sometimes it’s impossible to avoid using an image as a div background when coding in the front end. If it’s a small image, we can usually get away with loading times because the image loads almost instantly. But what if you have to use a large or high-resolution image? Users might be staring at a blank screen or some level-by-level loading image until it is fully loaded, which is not the optimal user experience you want.

Today I will tell you how to make the div background lazy load so users can have a blurred picture until it loads instead of a blank or half-loaded one.

Assuming you have a React project set up already, I will skip that part and jump directly to the custom component.

So in your React project, create a component called “LazyBackgroundImage” (you can name it whatever you want).

It should look like this:

import React from 'react'

type Props = {}

export default function LazyBackgroundImage({}: Props) {
  return (
    <div>LazyBackgroundImage</div>
  )
}

Once we create that, let’s make some props for our new component. The props we need are img(image URL), children(stuff that goes inside our div), and styles (any other styles you want to add). So once you add those, your code should look like this.

import React from "react";

type Props = {
  img: string;
  children?: JSX.Element[] | JSX.Element;
  style?: React.CSSProperties;
};

export default function LazyBackgroundImage({
  img,
  children,
  className,
}: Props) {
  return <div>{children}</div>;
}

As you can see, I have made children and style optional, but you can change them to your liking and requirement.

Now let’s add our image as our background of the div. Once you add the image and set up basic styling for the background image, like position, repeatability, size, etc., we should have something like this.

import React from "react";

type Props = {
  img: string;
  children?: JSX.Element[] | JSX.Element;
  style?: React.CSSProperties;
};

export default function LazyBackgroundImage({ img, children, style }: Props) {
  return (
    <div
      style={{
        backgroundImage: `url(${img})`,
        backgroundRepeat: "no-repeat",
        backgroundPosition: "center center",
        backgroundSize: "cover",
        ...style,
      }}
    >
      <div>{children}</div>
    </div>
  );
}

Now let’s start lazy loading. First, we need to keep track of the image state, whether it’s loaded or not. We can use a simple useState hook to serve our purpose. Also, to change its state, we need to create a function called handleLoad.

const [loaded, setLoaded] = useState(false);

  const handleLoad = () => {
    setLoaded(true);
  };

You might be wondering where we will use that “handleLoad” function. By default, “img” tags have a DOM attribute called “onLoad” built into them. So we will use the power of that attribute to manage our state using the “handleLoad” function. So above your “children” section, create an img tag using the image as the source. Also, we need to pass the “handleLoad” function to the “onLoad” attribute and set this img tag hidden so it will not mess up our UI.

<img src={img} alt="" onLoad={handleLoad} style={{ display: "none" }} />

Also, you can prevent the div children from loading until the image is loaded fully. Also, we need to add some filters to blur out our image until it's loaded, so we can add some inline styles to handle that easily.

Once we finish those steps, we have a component like this.

import React, { useState } from "react";

type Props = {
  img: string;
  children?: JSX.Element[] | JSX.Element;
  style?: React.CSSProperties;
};

export default function LazyBackgroundImage({ img, children, style }: Props) {
     const [loaded, setLoaded] = useState(false);

     const handleLoad = () => {
       setLoaded(true);
     };


  return (
    <div
      style={{
        backgroundImage: `url(${img})`,
        backgroundRepeat: "no-repeat",
        backgroundPosition: "center center",
        backgroundSize: "cover",
        filter: loaded ? "none" : "blur(20px)",
        transition: "filter 0.5s",
        ...style,
      }}
    >
      <img src={img} alt="" onLoad={handleLoad} style={{ display: "none" }} />
      {loaded && children}
    </div>
  );
}

Now whenever you want to use this inside your code, you must import this component and wrap the content using this lazy load div. Here’s an example.

  • With Children

      import LazyBackgroundImg from "@/components/Common/LazyBackgroundImg";
    
      export default function Test() {
        return (
          <LazyBackgroundImg img="https://images.unsplash.com/photo-1617854818583-09e7f077a156">
            <h3> Hello lazy load image!!!!</h3>
          </LazyBackgroundImg>
        );
      }
    
  • Without children (Use a self-closing div)

      import LazyBackgroundImg from "@/components/Common/LazyBackgroundImg";
    
      export default function Test() {
        return (
          <LazyBackgroundImg img="https://images.unsplash.com/photo-1617854818583-09e7f077a156"/>
        );
      }
    

If you feel fancy and want to add a little darkened state so the image would not block your “children” content, you can add the following styles to your background image.

import React, { useState } from "react";

type Props = {
  img: string;
  children?: JSX.Element[] | JSX.Element;
  style?: React.CSSProperties;
  isDarkened?: boolean;
};

export default function LazyBackgroundImg({
  img,
  children,
  className,
  style,
  isDarkened,
}: Props) {
  const [loaded, setLoaded] = useState(false);

  const handleLoad = () => {
    setLoaded(true);
  };

  return (
    <div
      style={{
        backgroundImage: `${
          isDarkened
            ? "linear-gradient( rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6) ),"
            : ""
        }url(${img})`,
        backgroundRepeat: "no-repeat",
        backgroundPosition: "center center",
        backgroundSize: "cover",
        filter: loaded ? "none" : "blur(20px)",
        transition: "filter 0.5s",
        ...style,
      }}
    >
      <img src={img} alt="" onLoad={handleLoad} style={{ display: "none" }} />
      {loaded && children}
    </div>
  );
}

Let me know if you had any trouble setting this up, I hope this will help you to make your app User experience better.