Hugo

Low Quality Image Placeholder (LQIP) Pipes

Hugo pipes have treated me well. Where would low quality image placeholder (LQIP) libraries fit in?

I’ve been able in the past to use Hugo pipes to export a low resolution placeholder and inline it as base64, but there are some cool libraries surfacing that make a much cooler spectacle of it.

There is a really popular library potrace (141,385 weekly downloads) that generates svgs from images. The following examples are svgs drawn by hand, potrace’s svgs are even better, but nonetheless it demonstrates the resulting effect.

And sqip does cool LQIPs (2,600 GitHub stars).

Both libraries are available as npm packages, so creating a custom per-project implementation beside Hugo wouldn’t be terribly difficult. Though, if Hugo could provide direct support for these sorts of libraries that are gaining more and more attention, maybe through a pipe like with postcss, it would make developing great sites even easier.

I am not sure where to start with this sort of possible integration with Hugo, which is why I am posting about it for some guidance.

You will probably end up with a build script that is running Hugo and creates your images, and then running over those images to create the low resolution with one of these libraries.

If you expect an easy way to link these libraries into your pipe processing then, nope, not possible (yet).

What might be possible is to use a srcset setup that has a very small image size to begin with, then use those images as preview (background image, scaled up to 100% width and height) until the proper images is loaded (thus overlaying the background image). You won’t achieve any of these artistic approaches though. More like a very blurry image (what we see in the LQIP preview).

Yeah, I am building out a pre-start script that processes the placeholders and labels the files predictably to be base64 inlined by Hugo.

I’ve never written Go, so I create a PR for this, or suggest how difficult it would be, but I think that adding a LQIP potrace or sqip pipe with a dict for options looks very similar to how other pips seem to work.

I am currently using the Gaussian blur filter to create small preview images. While it might not come with the desired oomph factor of the npm packages above, I think it’s good enough:

{{ $resource := .Resources.GetMatch "images/project.jpg" }}
{{ $projectImg_lazy := $resource.Resize "40x Gaussian" }}
{{ $projectImg_lazy = $projectImg_lazy.Content | base64Encode }}
<img class="lazy" data-src="{{ $projectImg.RelPermalink }}" alt=""
  src='{{ print "data:image/jpeg;base64," $projectImg_lazy | safeURL }}' />

Now if only Hugo provided a way to control how much the image gets blurred…

I would say Hugo “provides” it. By using the underlying library.

Try adding a .5 right behind Gaussian? I wonder if it’s possible to add additional parameters like this - if not, it should maybe an advanced feature.

I am afraid it does not. I might be wrong of course.

I think this works easier (for your solution, not the OPs): resize the image to something like 50px on the longest side (or 50x50), then set it as background image via style attribute as CDATA. Then add a blur via CSS (https://css-tricks.com/almanac/properties/f/filter/). Then the original images via srcset and src attributes. I think this way is best in multiple ways. No JS, newest browsers do what we expect, older browser show something pixelated, but people using old browsers will be accustomed to that. It will also rank better in speed-tools and “seo” checkers.

That’s definitely a quick way of doing it.

I see the lazy class and data-src attribute you have, so I am guessing you are using JavaScript to later set the actual src. The problem I found with that approach is when I swapped the src attribute, there sometimes is a flash of background color as the src changes. If you have that problem, a remedy I am using now is…

Remedy

… wrapping the img in a div and giving the img an absolutely positioned sibling div the width and size of the parent with a z-index that places it above the actual img tag. Inline the placeholder div a background-image set to the base64 of your placeholder image. When the HD img is loaded (use the image’s onload attribute to know when), transition the placeholder’s opacity to 0. That will reveal the HD image beneath it smoothly instead of potentially flashing white (in my case).

That’s pretty much what I would do. Added javascript can be useful for selectively loading only the currently visible images using either the observer api or a bounding rect calculation.

A quick Google find this:

It doesn not have many stars, but maybe it’s great …

We could certainly add a $image.Trace method or something that would produce a SVG.

There is also a related issue on GitHub about a $image.Overlay (overlay images on top, probably also with some simple font/text support).

1 Like

I have not tested the above, but the code looks good, and I created this:

2 Likes

Thanks for looking into this @bep. :+1:

I will do this: https://github.com/gohugoio/hugo/issues/6234

Then it will maybe not be so daunting for others to do add image related stuff.

That would be great, Bep.

Just as a side note (feel free to move my little suggestion into a new topic):

Browsers will support (and some already do) native lazy loading.

Maybe it would make sense to support this (now or later) along with the new intrinsicsize parameter in the Hugo figure shortcode.

Wow @Grob, that’s exciting. Though, I already can imagine the pain of waiting for other browsers to adopt it. Until they do, we will still have to create fallbacks manually.