Update from the future: I later made considerable changes to the methods described in this post. So that you’ll have a fuller perspective, I suggest you read everything except the code here, and only then go to my post about why I made the changes, where superior code awaits.
With today’s release of version 0.104.0 of the Hugo static site generator (SSG), there’s a new and perhaps better way to generate low-quality image placeholders (LQIPs).
As you probably already know, using an LQIP as an image’s background allows those with slower connectivity and devices to get at least a representation of a full image while that image is still downloading.
My go-to approach for LQIP-building has been, for each image, to Base64-encode a tiny version of that image and then load it as the background. The tiny reproduction would obviously be extremely blurred as it was stretched out to the real image’s width and height, producing the familiar “blur-up” effect when the real image appeared (fading in via CSS).
Then, today, the release of Hugo 0.104.0 added a .Colors
method to Hugo’s existing image-processing capabilities. As the documentation says (and remember that a slice is what Hugo, like the Go language on which it is based, calls what most other SSGs and languages call an array):
.Colors
returns a slice of hex string with the dominant colors in the image[,] using a simple histogram method.
(Link added.)
For example, given this1 image:

. . . here’s the slice you get from .Colors
:
#1d1816,#cac7c0,#978779
Those are the colors, in hex format, that .Colors
picked out as the main ones in the image. The number of colors obviously varies from image to image, and it can be as low as one. Note that a one-item slice will be problematic if you try to code so that an image’s .Colors
-derived LQIP has a background that’s a linear gradient, because the linear-gradient
function requires at least two colors. Thus, you’ll want to add a test for a one-color return from .Colors
so you can append to the slice an extra color — perhaps a safe one like black — if need be.
So, with Hugo 0.104.0 and up, you can try something like the following, in which $src
represents the real image file:
{{- $LQIP_colors := $src.Colors -}}
{{- if (lt ($LQIP_colors | len) 2) -}}
{{- $LQIP_colors = $LQIP_colors | append "#000000" -}}
{{- end -}}
{{- $LQIP_bkgd := delimit ($LQIP_colors) ", " -}}
{{- $style := print "background: linear-gradient(" $LQIP_bkgd "); background-size: cover; background-repeat: no-repeat;" -}}
After specifying that $LQIP_colors
is the return from $src.Colors
, we have the conditional for handling a one-color return: “if the length of $LQIP_colors
is less than 2, append
to $LQIP_colors
the color black (#000000
).” Right after that, we:
- Create
$LQIP_bkgd
, which is a string containing the contents of$LQIP_colors
, with each pair separated by adelimit
-ing comma and space. That’s for . . . - . . .
$style
, the CSS we’ll provide to the eventualdiv
in which the real image will be contained.2
Finally, be sure to check the $.Colors
example page, and its source code, mentioned in the 0.104.0 release notes.
Later note: I subsequently found a 2018 article which suggested the result of this approach should be considered as not an LQIP but, rather, a GIP (gradient image placeholder). Fair enough — and please, folks, let’s avoid any .gif-style arguments about how to pronounce the G of GIP.
If the site is currently on Hugo, the image is rendered using a shortcode that makes use of
.Colors
for demonstrative purposes. ↩︎If you have a strict Content Security Policy and need to work around the issue of inline styling, you might want to examine the admittedly more complicated original from which I extracted this example. ↩︎
Latest commit (08cb79e2
) for page file:
2023-04-17 at 6:18:22 PM CDT.
Page history