CSSJavaScriptTutorials

How to Build Animated Image Placeholder for Lazy-loading Images

lazyloaded

As has been noted several times in the past, images are the biggest bottleneck in so far as performance is considered. Web-developers have invented workarounds to fix this. Lazy-loading images is one such nifty trick. Essentially, with lazy-loading, images are loaded only when they enter the viewport.

However, there is one problem in this method. When the images enter the viewport and they are being loaded, a white section is shown in place of the image. This is poor UX because the user is left guessing what the white section eventually load. In this short tutorial, I am going to show you how you can fix this.

Here is a preview of what we are going to build:

Demo

(Click on Rerun to see it in action)

See the Pen Animated image placeholder by Abhisek Dutta (@0xfa1l) on CodePen.

Tools

For this tutorial, we would use Lazysizes javascript library. Lazysizes is a high perfomant SEO friendly lazy loader for images. 

Getting started

Let’s get started by creating an index.html file. Also, create a style.css file inside the root directory.

HTML Structure

<div class='flex'>
<div class='image-container'> <img src='//source.unsplash.com/random/370x270' class="lazyload"/> <div class='curtain'> <div class='shine'></div> </div> </div> <div class='image-container'> <img src='//source.unsplash.com/user/vanschneider/370x270' class="lazyload"/> <div class='curtain'> <div class='shine'></div> </div> </div> </div><!--/flex-->

This is fairly simple structure: div with classname flex will contain image-container. Each image-container hosts an img and .curtain. The .curtain element serves as the image placeholder and will show up when the image is being loaded. Also, the .shine element will add an animated foreground to the placeholder.

Add style to the elements

Next, we add some basic styles to the above html. In your style.css, add the following:

.flex {
  display:flex;
  flex-wrap:wrap;
}

.image-container {
  position:relative;
  width:370px;
  height:270px;
  margin:20px; /* demo purpose */
}

img {
  width: 100%;
  height: auto;
}

With the above CSS, each child element of .flex becomes flexbox elements. (This is for demo purpose only). Next, .image-container is given position:relative and height and width are set to 270px and 370px respectively. To make the images responsive, I have set width to 100% and height to auto for img elements.

This, by itself, does nothing. So, further add styles for .curtain

.curtain {
  position:absolute;
  top:0;
  left:0;
  width:100%;
  height:100%;
  background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSJ3aGl0ZSI+CiAgPHBhdGggZD0iTTAgNCBMMCAyOCBMMzIgMjggTDMyIDQgeiBNNCAyNCBMMTAgMTAgTDE1IDE4IEwxOCAxNCBMMjQgMjR6IE0yNSA3IEE0IDQgMCAwIDEgMjUgMTUgQTQgNCAwIDAgMSAyNSA3Ij48L3BhdGg+Cjwvc3ZnPg==") no-repeat center hsl(0, 0%, 80%);
  background-size: calc(100%/3);
  overflow:hidden;
  z-index:20;
}

Now, .curtain stretches to fit entire height and width of img elements. z-index is set to 20 to stack .curtain above img elements.

Next, we need to add the animated shine effect to .curtain. To do so, add this to style.css

.shine {
    width: 100%;
    height: 100%;
    transition: 0.3s;
    background: linear-gradient(-90deg, #efefef 0%, #fcfcfc 50%, #efefef 100%);
    background-size: 400% 400%;
    -webkit-animation: shine 1.3s infinite;
    animation: shine 1.3s infinite;
    opacity:0.8;
}

@-webkit-keyframes shine {
  0% {
      background-position: 0% 0%;
  }
  100% {
      background-position: -135% 0%;
  }
}
@keyframes shine {
  0% {
      background-position: 0% 0%;
  }
  100% {
      background-position: -135% 0%;
  }
}

If you look closely, the above css essentially adds linear-gradient to .shine elements, and then, it moves the gradient (with background-position) to give an animated feel.

At this point, if you reload the page, the entire image is blocked with .curtain. We need to remove .curtain once images are loaded.

Add some JavaScript trickery

To remove the “curtain” once images are loaded, add jQuery and LazySizes to your index.html

<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/lazysizes/4.1.4/lazysizes.min.js'></script>

Next, add the following inside <script> tag:

$(".image-container img").one('lazyloaded load', function() {
  $(this).parent().find(".curtain").remove();
}).each(function(e){
  if(this.complete){
    $(this).trigger("lazyloaded load");
    $(this).parent().find(".curtain").remove();
  }
});

The above code removes each .curtain once images are loaded (I have used native load event and lazyloaded event that is fired when images are loaded via LazySizes plugin). It is worthwhile to note here that without .each part of the code, we won’t be able to ‘catch’ load or lazyloaded event if images are loaded from cache. this.complete checks if images are loaded from cache. If so, it fires load and lazyloaded events.

Conclusion

Lazy-loading images is a very well-known technique and widely used. However, showing the user some kind of placeholder instead of empty space often a better practice. This post is aimed at showing you how you can improve on the user-experience with placeholder images.