Medium-like lazy loading of images

I was looking for a good way to lazy load image son my website and stumbled upon https://www.botreetechnologies.com/blog/page-load-optimization-by-progressive-image-loading-like-medium. This post describes how to do image lazy loading similar to what medium does

  • Show a div the same size as the resulting image (so the content does not “jump” after the image is loaded)
  • Load a very small low quality placeholder and blur it in the div
  • Load the final image

I made it into a reusable shortcode

{{ $image := (.Page.Resources.GetMatch  (index .Params 0)) }}
{{ $placeholder := $image.Resize "48x q20" }} 

<div class="image_placeholder" style="max-width: {{$image.Width}}px">
<div class="placeholder" data-large="{{ $image.Permalink }}">  
  <img src="{{ $placeholder.Permalink }}" class="img-small">
  <div style="padding-bottom: {{ div (mul $image.Height 100.0) $image.Width }}%;"></div>
</div>
</div>

that can be called using

{{% post-image "image.png" %}}

but only works with page bundle images to be able to use the Hugo image resizing functionality.

The JavaScript and CSS stay the nearly same as in the post, I just adjusted them for my theme

.placeholder {
  background-color: #f6f6f6;
  background-size: cover;
  background-repeat: no-repeat;
  position: relative;
  overflow: hidden;
}

.placeholder img {
  position: absolute;
  opacity: 0;
  top: 0;
  left: 0;
  width: 100%;
  transition: opacity 1s linear;
}

.img-small {
  filter: blur(50px);
  /* this is needed so Safari keeps sharp edges */
  transform: scale(1);
}

.placeholder img.loaded {
  opacity: 1;
}

.image_placeholder{  
  display: block;
}
<script type="text/javascript">
window.onload = function() {

  var placeholder = $('.placeholder');

  placeholder.each( function(index) {
    // 1: load small image and show it
    var smallImgElement = $(this).find('.img-small');
    var img = new Image();
    img.src = smallImgElement.attr('src');
    img.onload = function () {
      smallImgElement.addClass('loaded');
    };

    // 2: load large image
    var imgLarge = new Image();
    imgLarge.src = $(this).data('large');
    imgLarge.onload = function () {
      imgLarge.classList.add('loaded');
    };
    $(this).append(imgLarge);

  })

}
</script>

You can see it in action at https://blog.kulman.sk/, especially in the Projects section.

15 Likes

This is pretty cool. And you can also use the hasShortcode function to only load the JS in page where it is being used. :slight_smile:

Thanks for this!! I modified it a bit to use only vanilla JS in case anyone’s interested.

window.onload = function() {

  var placeholder = document.querySelectorAll('.progressive_figure');

  placeholder.forEach(element => {
    var smallImage = element.getElementsByClassName('img-small')[0];

    // Load placeholder image
    var img = new Image();
    img.src = smallImage.getAttribute('src')
    img.onload = function () {
      smallImage.classList.add('loaded')
    }
    
    // Load large image
    var largeImage = new Image();
    largeImage.srcset = element.getAttribute('data-imgset');
    largeImage.src = element.getAttribute('data-src');
    largeImage.classList.add('lozad')
    largeImage.onload = function () {
      largeImage.classList.add('loaded');
      smallImage.replaceWith(largeImage);
    }
})};
4 Likes

I haven’t really read the detail of this thread, but this link is very important: https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/

It provides guidance on how to do lazy loading images so they can be seen by Google.

1 Like

From what I understand (I’m only getting started with JS), the technique in the OP of this thread is about only the progressive-loading part of lazy loading.

For lazy loading (loading image only when scrolled to), you’ll have to use the Intersection Observer etc, as described in the Google link. This library does it quite well if you’re interested.

2 Likes

One new trick. Instead of linking the 48px placeholder image, you can just generate a much smaller image, say 2px and embed it directly to the HTML as base64:

{{ $image := (.Page.Resources.GetMatch (index .Params 0)) }}
{{ $placeholder := $image.Resize “2x q20” }}

<div class="image_placeholder" style="max-width: {{  $image.Width }}px">
<div class="placeholder" data-large="{{ $image.Permalink }}">  
  <img src="data:image/jpeg;base64,{{ $placeholder.Content | base64Encode }}" class="img-small loaded">
  <div style="padding-bottom: {{ div (mul $image.Height 100.0) $image.Width }}%;"></div>
</div>
</div>
4 Likes

my 2 cents

to avoid the content jumping around, put the image size in the html content, like this

<img class="imageclass" src="{{ $image.RelPermalink }}" width={{ $image.Width }} height={{ $image.Height }} />

With the right CSS-class you can define the needed borders etc.

@pritamps suggests a way to load images during scrolling - different beast

2 Likes