How To Lazy Load Images In React?

Tim Mouskhelichvili
Tim Mouskhelichvili
4 minutes to read

Images are often the most significant elements (size-wise) to load on a website. That's why it is best only to load the images that the user sees, and when the user scrolls, progressively load the others. This brings the question of how to lazy load images in React.

To lazy load an image in React, you can:

  • Add the loading attribute on the image
  • Use the IntersectionObserver to load the image on scroll
  • Use an npm library

This article will analyze those three solutions and show code examples for each.

Let's get to it 😎.

react lazy load images

Method #1 - Add the loading attribute

The easiest way to lazy load an image in React is to add the loading attribute on the image element.

The loading attribute supports three different values:

  • Lazy - To lazy load the image
  • Eager - To load the image right away
  • Auto - To let the browser decide whether to lazy load or not.

Nowadays, most browsers support this attribute.

Here is an example of lazy loading an image in React:

javascriptimport React from "react";
import { createRoot } from "react-dom/client";

const App = () => {
  const src = "https://timmousk.com/wp-content/uploads/2022/08/cover-9.png";

  return <img src={src} loading={"lazy"} alt="" />;
};

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(<App />);

In this code example, we create a component that lazy loads an image (thumbnail of this blog).

Unfortunately, although this method is simple, it doesn't give you much control over how you want to load the image.

For example, controlling the distance from the viewport threshold is impossible.

If you need specific control over how the image loads, you need to use an npm library or the following method.

Method #2 - Use the IntersectionObserver

The IntersectionObserver is an API that allows observing changes in the intersection of a target element.

Simply put, it notifies you when an element enters the document's viewport.

Using the IntersectionObserver API, we have a lot of control over the implementation of lazy loading for images in our React application.

Note: The IntersectionObserver is available on all new browsers.

Here is an implementation of a lazy loaded image with the IntersectionObserver:

javascriptimport React, { useState, useEffect, useRef, useCallback } from "react";
import { createRoot } from "react-dom/client";

let listenerCallbacks = new WeakMap();

const handleIntersections = (entries) => {
  entries.forEach((entry) => {
    if (!listenerCallbacks.has(entry.target)) {
      return;
    }

    let callback = listenerCallbacks.get(entry.target);

    if (!entry.isIntersecting) {
      return;
    }

    observer.unobserve(entry.target);
    listenerCallbacks.delete(entry.target);
    callback();
  });
};

let observer = new IntersectionObserver(handleIntersections, {
  rootMargin: "0px",
  threshold: "0.15"
});

const useIntersection = (ref, callback) => {
  useEffect(() => {
    listenerCallbacks.set(ref.current, callback);
    observer.observe(ref.current);

    return () => {
      listenerCallbacks.delete(ref.current);
      observer.unobserve(ref.current);
    };
  }, [ref, callback]);
};

const Image = ({ src, width, height }) => {
  const [isInView, setIsInView] = useState(false);
  const ref = useRef();

  const callback = useCallback(() => {
    setIsInView(true);
  }, [setIsInView]);

  useIntersection(ref, callback);

  return (
    <div
      ref={ref}
      style={{
        paddingBottom: `${(height / width) * 100}%`,
        width: "100%"
      }}
    >
      {isInView && (
        <img
          src={src}
          alt=""
          style={{
            position: "absolute",
            width: "100%",
            height: "100%"
          }}
        />
      )}
    </div>
  );
};

const App = () => {
  const src = "https://timmousk.com/wp-content/uploads/2022/08/cover-9.png";

  return (
    <>
      <div
        style={{
          height: "2000px",
          width: "100%",
          position: "relative"
        }}
      ></div>
      <Image src={src} height={100} width={100} />
    </>
  );
};

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(<App />);

Read more: The forEach function in React

Here is what happens in the code:

  1. We define the IntersectionObserver (The rootMargin is the margin around the root and the threshold is the percentage of the target's visibility at which the callback should execute)
  2. Then we define the IntersectionObserver API's callback, which verifies if the element is intersecting.
  3. We create a hook called useIntersection that connects the image to the observer.
  4. The Image component calls the useIntersection hook and shows the placeholder div element or the image.

Note: The div element inside the App exists to move the image below the viewport for demonstration purposes.

Using the IntersectionObserver API is advanced JavaScript.

That's why to simplify the development, many developers choose to use an npm library instead.

Method #3 - Use an npm library

Finally, you can choose to use an npm library.

Luckily, there are a lot of different ones available on npm.

Here are a few popular ones:

Using an npm library instead of implementing it yourself can save you a lot of time for the development and the bug fixes.

Final thoughts

As you can see, it is easy to lazy load images in React.

Adding the loading attribute to the image element will be enough for most use cases.

For more complex use cases, you can choose between adding an npm library to your project or implementing lazy loading yourself.

react lazy load images

Here are some other React tutorials for you to enjoy:

Comments (0)
Reply to: