paint-brush
React Progressive with Lazy-Loading Images [A How-To Guide]by@sanishkr
6,545 reads
6,545 reads

React Progressive with Lazy-Loading Images [A How-To Guide]

by Sanish kumarMarch 1st, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

React Progressive with Lazy-Loading Images [A How-To Guide] Read the original article by Sanish kumar. The article is based on the ReactJS package, ReactJS. The package is a ReactJS version of ReactJS, a JavaScript-JS toolkit. It uses ReactJS' ability to load images in the browser. It can be easily done with a <Image/> component in ReactJS with one or two of the above features is easy, but how about building an <Image/loader component with all of the. above features? Enter the superhero package…

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - React Progressive with Lazy-Loading Images [A How-To Guide]
Sanish kumar HackerNoon profile picture

npm i react-progressive-graceful-imageSupports features like Custom Image Placeholder/Loader Component, srcset, lazy Loading, Graceful Loading, Progressive Image Loading.Use of Intersection Observer and navigator.onLine (Better performance and Optimization)

Motivation

I was working on an e-commerce PWA and you know what is there on an e-commerce platform? Images, more images and well, a lot more images.

The truth is, with the increase in internet speed, images are everywhere now, be it e-commerce/social media/blog/news/dating or any other site.

Image is Everything, Everything is Image — (Credit: Rockstar movie)

So what’s the big deal in loading images? Just make an <img src=path-to-image /> tag and done!! I am sure you are thinking it’s not even that easy.

The problem is loading of an image takes time. Particularly, when there is a huge number of high-quality images on a single page.

Solutions

To give a better user experience, various things can be done:

Image Placeholder — Keep a static image(png/svg) / a loader(gif) / a colored background div(usually grey) / a grey div with shimmer effect like the one below, until the real image is downloaded.

(Image Loading with Shimmer effect animated placeholder)

Progressive JPEG — Use Progressive JPEG instead of a normal Baseline JPEG image, although it’s not much in the control of client-side code. Here is an example.

Baseline Image vs Progressive Image LoadingLazy Loading — Don’t load an image until it is visible or close to the viewport. Below gif explains it.

(Lazy loading)

Graceful Loading — Display the placeholder until the image is fully loaded and retry image loading even if network error happens(mostly when a device switches network for some reason like power/network failure).

Progressive Image Loading — Sites like medium, facebook, etc uses a tiny version of the real image with blur filter until the real image is downloaded and then uses some CSS transition to replace the tiny one. Here is an example.

(Progressive Image Loading)

srcset attribute —used to let the browser automatically choose image based on device screen size and/or device pixel ratio(DPR).

Huh? That’s a lot of information and so many things can be done. Now to build an <Image/> component in ReactJS with one or two of the above features is easy, but how about building an <Image/> component with all of the above features?

Drumroll, please!! Enter the savior, the superhero npm package…

npm i react-progressive-graceful-image

Some FAQs

Why do we need various combinations together, why not implement just one variation, for example - Progressive Image Loading?

— Let’s take an example. Home Page of an e-commerce has various sections, some with ads for which tiny images might be available. Since it’s a landing page that gets maximum eyeballs of visitors, hence better user experience is required.

But “Search result page” or “Product description page” may serve images for which tiny images are not available(why you ask? One of the reasons, it’s costly).

So we need let’s say, a grey div placeholder with shimmer effect. And as a developer, you don’t want to make multiple components for images. Similarly, there can be other use-cases. You get my point.

Why do we use this npm package? Is there any other better package?

— I found some really good packages like react-shimmerreact-graceful-image, and react-progressive-image but honestly speaking, most of them lacked one or more important features.

Like, react-graceful-image lacked shimmer effect and react-progressive-image lacked lazy-loading and graceful-loading. In fact, I started by forking react-progressive-image and adding new features from react-graceful-image. Later, I improved upon them.

What about performance, optimization, and browser support?

— Currently, the package size is 3.6kb. I have used Intersection Observer for Lazy Loading (Better Performance) as compared to throttling scroll event listeners which run on the main thread.

One big reason, Intersection Observer runs on a separate thread in the Browser. Read more about it here and see it in action at @researchgate/react-intersection-observer which is the only dependency of this package.

Also, the retry strategy for graceful loading in react-graceful-image was not quite optimized. It would also hang the screen sometimes when the device goes offline and a lot of image requests are sent simultaneously which would fail anyway. So I have used navigator.onLine based strategy for optimization.

In case Intersection Observer is not supported by a browser, its polyfill is available here.

Usage

Great!! How do I use it? Any demo?

Here you go…

You can find more examples and documentation on npm or github.

In case you find any issues or improvements, PRs and contributions are welcome on github.

Extra Browny Stuff: Always use padding-percentage solution to wrap your custom <Image/> component to prevent any reflow and improve browser performance as explained in this article

Here is a small code snippet to make the best use of this logic with this npm package.

import React from 'react';
import ProgressiveImage from 'react-progressive-graceful-image';
import styled from 'styled-components';

const PlcHolder = styled.img`
  background: linear-gradient(
    to right,
    rgb(246, 247, 248) 0%,
    rgb(237, 238, 241) 20%,
    rgb(246, 247, 248) 40%,
    rgb(246, 247, 248) 100%
  );
  @keyframes placeHolderShimmer {
    0% {
      background-position: top right;
    }
    100% {
      background-position: top left;
    }
  }
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  position: absolute;
  background-size: 2250px 2250px !important;
  background-repeat: no-repeat !important;
  background-position: 0 0 !important;
  animation: placeHolderShimmer 1.5s ease-in-out infinite;
`;
const ImageParent = styled.div`
  position: relative;
  width: 100%;
`;
const StyledImage = styled.img`
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  position: absolute;
`;

export default ({
  width = 414,
  height = 184,
  srcSetData,
  src,
  placeholder,
  alt = 'Image',
}) => {
  return (
    <ImageParent
      style={{
        paddingBottom: (height / width) * 100 + '%',
      }}
    >
      <ProgressiveImage
        srcSetData={{
          srcSet: srcSetData.srcSet,
          srcSet: srcSetData.sizes,
        }}
        src={src}
        placeholder={placeholder}
      >
        {(src, loading, srcSetData) => {
          return !placeholder ? (
            loading ? (
              <PlcHolder
                src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'200' height%3D'150' viewBox%3D'0 0 200 150'%2F%3E"
                w={width}
                h={height}
              />
            ) : (
              <StyledImage
                srcSet={srcSetData.srcSet}
                sizes={srcSetData.sizes}
                src={src}
                alt={alt}
              />
            )
          ) : (
            <StyledImage
              srcSet={srcSetData.srcSet}
              style={{ filter: loading ? 'blur(15px)' : 'blur(0px)' }}
              sizes={srcSetData.sizes}
              src={src}
              alt={alt}
            />
          );
        }}
      </ProgressiveImage>
    </ImageParent>
  );
};

Thank you for reading this article. Please share and clap if you liked it.

Let me know in the comments below if you want me to give more details about the code implementation of this npm package, as some have asked me personally.