How to create a kick-ass image preview with LQIP
Tags: design, webdev, ux, react
Published: 2019-07-11 13:40:09

Images in HTML, what could be easier? However when you have many of them on a page, they do not appear immediately. That depends on caching strategy and bandwidth, but still if you don’t take a special care it may look quite ugly. Basically we need to fill in the slots with something appropriate while images are loading. In other words we need placeholders. Probably the most prominent technique here is LQIP ([low quality image placeholder](https://www.guypo.com/introducing-lqip-low-quality-image-placeholders "low quality image placeholder")). It was adopted by Google, Facebook, Pinterest, Medium and others. The idea is to load page initially with low quality images and once the page is fully loaded replace them with full quality ones. As placeholder one can use embedded transparent SVG, spinner animated image, solid color, blurred and minified original image. But even more, with modern tools we can do something really fancy. For example, we can use images shape or silhouette as a placeholder. Moreover, we can generate Data-URLs with desired effect during the build and address from IMG tag.

  

The basics
----------

Let’s get there step by step. First we come back to the basics. HTML IMG tag didn’t change much for last 30 years:

```html
    <img alt="Lorem ipsum" src="./img/test.jpg" />
```

Yet , we have now `srcset` attribute to tackle responsive web design:

```html
    <img srcset="./img/test-1x.jpg 1x,
                 ./img/test-2x.jpg 2x"
         src="./img/test.jpg" alt="Lorem ipsum" />
```

Here we enlist image sources per display density (1x, 2x). Thus browser will load double sized version (test-2x.jpg) on Retina devices. Or we can be more specific:

```html
    <img srcset="./img/test-320w.jpg 320w,
                 ./img/test-480w.jpg 480w,
                 ./img/test-800w.jpg 800w"
         src="./img/test.jpg" alt="Lorem ipsum" />
```

Now we specify image source width (320w, 480w, 800w) and browser will use that information to pick the most suited source. Note that we still use `src` attribute to specify fallback source, which will be used by legacy browsers.

Now, to the point. What can we do to beautify image loading? The simplest thing is to add an animated spinner as background for image slots. So while image are loading we see the animation. As the loading completed, we see the images covering the background.

But what if some images fail to load? Diverse browsers render “broken” images differently, but equally awful. To fix it you can target some of them with CSS. However, most universal way, I assume, is to use JavaScript:
```js
    Array.from( document.querySelectorAll( "img:not(.is-processed)" ) ).forEach( img => {
        img.classList.add( "is-processed" );
        img.addEventListener( "error", () => {      
          img.style.opacity = 0;
        }, false );
      });
```
  

Lazysizes
---------

Alternatively we can go with a loader library [Lazysizes](https://github.com/aFarkas/lazysizes "Lazysizes ") to achieve better perceived performance. It unlocks new options. For example, we can achieve empty image placeholder like that:

```html
    <img    
      src="./img/test-fallback.jpg"
      srcset="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
    	data-srcset="./img/test-320w.jpg 320w,
            ./img/test-480w.jpg 480w,
            ./img/test-800w.jpg 800w"
    	data-sizes="auto"
    	class="lazyload" />
```

Thus the browser will show the embedded placeholder (transparent or low quality image) until it loads an image corresponding to the viewport from `data-srcset`.

Lazysizes adds `lazyloaded` CSS class to image element on `load` event and that we can use, for an instance, to implement blur-up placeholder:

```html
    <style>
    	.blur-up {
    		-webkit-filter: blur(5px);
    		filter: blur(5px);
    		transition: filter 400ms, -webkit-filter 400ms;
    	}
    
    	.blur-up.lazyloaded {
    		-webkit-filter: blur(0);
    		filter: blur(0);
    	}
    </style>
    <img src="./img/test-lqip.jpg" data-src="./img/test.jpg" class="lazyload blur-up" />
```
So the low quality image (test-lqip.jpg) will be blurred until the original image (test.jpg) loaded.

In the article [How to use SVG as a Placeholder, and Other Image Loading Techniques](https://www.freecodecamp.org/news/using-svg-as-placeholders-more-image-loading-techniques-bed1b810ab2c/ "How to use SVG as a Placeholder, and Other Image Loading Techniques") you can find insights of LQIP techniques with drawing effect, based on shapes and silhouettes. Why not put it in practice? So we have to generate a low-quality image, precisely, a Data-URL with SVGO and specify it in `src` or `srcset` attribute of IMG tag, while full-quality image sources we set in `data-srcset`, pretty much as we examined above. The most convenient way to achieve it would be with [Webpack](https://webpack.js.org/). The tool transforms imported images during the build. So we can refer the result (e.g. generated SVGO) straight in the application code. Let’s see in practice.

First, we install dependencies:
```
    npm i -S lazysizes react react-dom
```

As you see we are going to use Lazysizes library and React.js.

Now it’s the turn to install developer dependencies. We start with babel packages:

```
    npm i -D @babel/cli @babel/core @babel/node @babel/preset-env @babel/preset-react babel-loader
```

Then go Webpack ones:
```
    npm i -D webpack webpack-cli clean-webpack-plugin   file-loader image-webpack-loader
```

The [file-loader](https://github.com/webpack-contrib/file-loader "file-loader") plugin makes Webpack resolving imports of images and [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader "image-webpack-loader") optimizes imported

As we have dependencies, we can create [base webpack configuration](https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/webpack.base.js "base webpack configuration") for React.js/Babel application. We put in `src/img` test-1x.jpg and double-sized test-2x.jpg demo images and to `src/index.jsx` the entry script:

```jsx
    import React from "react";
    import { render } from "react-dom";
    import Image from "./component/Image";
    import "lazysizes";    
    import productImg1x from "./img/test-1x.jpg";
    import productImg2x from "./img/test-2x.jpg";
    
    render(
      <Image
          placeholder={ productImg1x }
          srcSet={[ productImg1x, productImg2x ]}
          alt="A farm"
          ></Image>,
      document.getElementById( "app" )
    );
```

Here we load lazysizes library, importing out both images and passing them to Image component. The [HTML file](https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/index.html "HTML file") may look like that

```html
    <div id="app"></div>
    <script src="build/index.js"></script>
```
  

Silhouette
----------

Silhouette placeholder we can generate with [image-trace-loader](https://github.com/EmilTholin/image-trace-loader "image-trace-loader ") . The plugin extracts image outlines and returns them as SVGO.

We have to extend our Webpack configuration with the following:
```
module: {
  rules: [
    {
      test: /\.(gif|png|jpe?g)$/i,
      use: [
        {
          loader: "image-trace-loader"
        },
        {
          loader: "file-loader",
          options: {
            name: "src-[name].[ext]"
          }
        },
        {
          loader: "image-webpack-loader",
          options: {
            bypassOnDebug: true, // webpack@1.x
            disable: true // webpack@2.x and newer
          }
        }
      ]
    }
  }
]    
```
  

Now in the code we can receive imported images as:
```js
    import { src, trace } from './image.png';
```  

Where trace is generated SVGO Data-URL and src the full-quality image. It gives us the following Image component:

[src/component/Image.jsx](https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/src/component/Image.jsx "src/component/Image.jsx")
```jsx
    import React from "react";
    
    export default function Image({ placeholder, alt, srcSet }) {
        return <img
          className="lazyload"
          alt={ alt }
          src={ placeholder.trace }
          data-srcset={ srcSet.map( ( img, inx ) => `${ img.src } ${ inx + 1}x` ).join( ", " ) }
          data-sizes="auto"
          />;
    }
```
 

Now we run Webpack and get the following loading frames:

![SQIP with silhouette](http://dsheiko.com/download/000000293/silhouette.gif "SQIP with silhouette")
 
  

Shape
-----

[Sqip-loader](https://github.com/EmilTholin/sqip-loader "Sqip-loader") split a given picture in arbitrary number of primitive shapes as as triangles, rectangles, ellipses, circles, polygons and others.

For shape-based placeholder in Webpack configuration the loader rule may look like:
```
{
  loader: "sqip-loader",
  options: {
    numberOfPrimitives: 20,
    mode: 1,
    blur: 0
  }
}
```

Here we require 20 triangle-based shapes and no blur. That makes image imports available in the code as follows:
```js
    import { src, preview } from './image.png';
```
  

Where preview is generated SVGO Data-URL and src the full-quality image. So we have to modify [src/component/Image.jsx](https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/src/component/Image.jsx "src/component/Image.jsx"). Instead of { placeholder.trace } we go with { placeholder.preview }.

Well, let’s run Webpack and check the page in the browser:

![SQIP with shapes](http://dsheiko.com/download/000000293/shapes.gif "SQIP with shapes")
  

Blur up
-------

This technique is often refereed as SQIP. While image load we see a blurred low quality placeholders similar to how it works on [Medium](https://medium.com/@jmperezperez/how-medium-does-progressive-image-loading-fd1e4dc1ee3d "Medium"). The placeholders can be also generated by [Sqip-loader](https://github.com/EmilTholin/sqip-loader "Sqip-loader"). However this time we are going to set blur:
```
{
  loader: "sqip-loader",
  options: {
    numberOfPrimitives: 20,
    mode: 1,
    blur: 30
  }
}
```

The result looks so:

![SQIP with blurup](http://dsheiko.com/download//000000293/blurup.gif "SQIP with blurup")

Recap
-----

We we brushed up on `src` and `srcset` image attributes. We learnt how to use them together with their data-counterparts and Lazysizes library to take advantage of LQIP technique. We set up Webpack and a simple React.js example to fiddle with three SQIP approaches: silhouette, shapes and blurup.

The full code source of the example can be found here:

*   [https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/](https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/)