I was looking into a lightweight solution for handling the loading of images on a slow connection. For that, I combined some ideas and came up with the following. I created a new shortcode image.html

{{< image src="watercooling-1.jpg" alt="Yellow tubes!" >}}

The job of this shortcode is to take the input image and retrieve its dimensions and the ratio of the height over the width

{{ $image := ( .Page.Resources.GetMatch  (index .Params.src)) }}
{{ $width := $image.Width }}
{{ $height := $image.Height }}
{{ $dimens := print $width "x" $height " q10" }}
{{ $ratio := div (add $height 0.0) $width }}

and generates the link to the image to be used in the html code (and retrieve the alt field)

{{ $src := "" }}
{{ $src = print $image.RelPermalink }}
{{ $alt := .Get "alt" }}

The ratio is used to stop content jumping, based on Javier Villanueva’s solution to the problem. This is done by creating a container div and adjusting the padding-top property to be 100% of the calculated ratio. The former is done in the common scss file

.img_container {
   position: relative;
}

and the latter as inline css

<div class="img_container" style=" height: 0; padding-top: calc( {{ print $ratio }} * 100%);>

Next, the image itself is given an absolute position in css with auto height adjustment and a maximum width of 100%

.img_contained {
  position: absolute;
  top: 0;
  left: 0;
  max-width: 100%;
  height: auto;
}

The image is then included, and additionally given the lazy loading propery

<div class="img_container" style=" height: 0; padding-top: calc( {{ print $ratio }} * 100%);>
<img class="img_contained" src="{{ $src }}" alt="{{ $alt }}" loading="lazy">
</div>

This takes care of content jumping, but now we end up with blank spaces until the images come in. To fix that, a fuzzy low-quality version of the same image is generated

{{ $placeholder := ($image.Resize $dimens) | images.Filter (images.GaussianBlur 6) }}
{{ $placeholder_src := print $placeholder.RelPermalink }}

and used as the background of both the div and the image. This gives a preview of the upcoming image to be loaded in

<div class="img_container" style=" height: 0; padding-top: calc( {{ print $ratio }} * 100%); background-image: url({{ $placeholder_src }}); background-size: cover;">
<img class="img_contained" src="{{ $src }}" alt="{{ $alt }}" loading="lazy" style="background-image: url({{ $placeholder_src }}); background-size: cover;">
</div>

Finally, I was inspired by Matt Hinchliffe’s approach to creating a simple gif free loading animation in css. This is done using a bobble animation

@keyframes bobble {
  0% {
    opacity: 0;
    transform: translateY(0);
  }

  35% {
    opacity: 1;
    transform: translateY(-40px);
  }

  100% {
    opacity: 0;
    transform: translateY(0);
  }
}

which is placed in the div container with the lowest z-index

.img_container::after {
  content: ' ';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2rem;
  height: 2rem;
  margin: -0.5em 0 0 -0.5em;
  background: rgba(125, 125, 125, 0.5);
  border-radius: 100%;
  animation: bobble 2s cubic-bezier(0.6, 1, 1, 1) infinite;
  z-index: -1;
}

Alltogether, this results in a lightweight, javascript-free solution for the lazy loading of images with some preview.