Shortcode for resizing images, lazy loading, AND progressive loading, all in one!


#1

I only just started using Hugo, and am learning JS as I use it, so suggestions for improvement are welcome!

I started off with this post, and then read a bit more and added more features. it also uses the Lozad JS library, so you’ll need to have that loaded.
This is what I use:

{{<srcset_image classes="vertical" name="new_profile_pic_edited.jpg" alt="Profile Picture" caption="Once upon a time in a cafe in Seattle... ">}}

Shortcode:

{{ $caption := .Get "caption"}}
{{ $classes := .Get "classes"}}

{{ $image := (.Page.Resources.GetMatch (.Get "name")) }}

{{ $placeholder := $image.Resize "48x q20" }}

{{ with $.Page.Resources.GetMatch (.Get "name") }}
<figure class="progressive_figure {{$classes}}" data-imgset="{{ (.Resize "640x").RelPermalink }} 320w,
  {{ (.Resize "1024x").RelPermalink }} 600w,
  {{ (.Resize "1600x").RelPermalink }} 2x"
  data-src="{{ (.Resize "1600x").RelPermalink}}" >
  <div class="placeholder">
    <img class="img-small" src="{{ $placeholder.RelPermalink }}" alt="{{$altText}}" />
  </div>
  <figcaption>{{ $caption }}</figcaption>
</figure>
{{ else }}
could not find image
{{ end }}

Javascript:

/* ---- BEGIN LAZY LOADING ----- */
const observer = lozad(); // lazy loads elements with default selector as '.lozad'

// Progressive section
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('img-large')
      largeImage.classList.add('lozad')
      largeImage.onload = function () {
        largeImage.classList.add('loaded');
        smallImage.replaceWith(largeImage);
      }
    }
})};

observer.observe();

CSS:

.img-small {
    filter: blur(25px);
    border: 1px solid $light-grey;
    border-radius: 5px;
  }

  .placeholder {
    background-color: rgba($color: #000000, $alpha: 0);
    background-size: cover;
    background-repeat: no-repeat;
    position: relative;
    overflow: hidden;
  }

  .placeholder {
    overflow: auto;
    border: 1px solid $light-grey;
    border-radius: 5px;
  }

  .placeholder .img-large {
    transition: opacity 0.2s linear;
  }

  .placeholder img.loaded {
    opacity: 1;
  }

  .placeholder {
    display: block;
  }

In action:

At the About section of my own website. I’ve only enabled it on the About page for now, because I imported my blog from Wordpress and the other entries are a markdown mess!


#2

Here are interesting articles for this topic:

With regards to (really) deferring javascript: https://varvy.com/pagespeed/defer-loading-javascript.html
With regards to images and lazy loading (without javascript): https://varvy.com/pagespeed/defer-images.html

Hope this helps.


#3

Thanks for the links! But I wasn’t posting a question – just some already working code that might maybe help someone looking to do the same thing!

As far as I can tell, the code I posted defers images properly (though I do use a library for lazy loading), and no jQuery is involved :slight_smile:.

The first link is about deferring javascript, while my code is for deferring images. My JS is so small (less than a KB), that I don’t really care (yet) about deferring it. Second link is about deferring images without jQuery, which I’m already not using, though I am using this library which is also less than a KB . It’s also slightly out of date because these days the recommended way is to use the IntersectionObserver with a fallback for browsers it doesn’t work on. Sounded complicated, hence the library.

Code for progressive image loading, i.e. load a low quality placeholder (deferred based on scroll) first and then load the large image in the background, the way Medium does it.