Skip to content

Instantly share code, notes, and snippets.

@cpbotha
Last active March 4, 2025 11:18
Show Gist options
  • Save cpbotha/deb310eed14308fe26f7b7d0fabeb34d to your computer and use it in GitHub Desktop.
Save cpbotha/deb310eed14308fe26f7b7d0fabeb34d to your computer and use it in GitHub Desktop.
Drop-in replacement for Hugo figure shortcode with img srcset support
{{/*
figure with auto-resizing and srcset v2024-11-24
Drop-in replacement for Hugo's figure shortcode which uses img srcset
to enable browsers to download only the resolution that they need.
The resizing and srcset magic only works for images that are part of the page
bundle. It will fall back to stock Hugo figure behaviour otherwise.
Improvements that were initially out of reach of my Hugo template programming "skills"
but have now been taken care of:
- [x] gracefully handle images that are not in page bundle, i.e. no image processing available
- [x] use a single configurable sizes array, and derive everything from there
See https://cpbotha.net/2020/05/02/drop-in-replacement-for-hugo-figure-shortcode-with-img-srcset-support/
- original srcset img shortcode from: https://laurakalbag.com/processing-responsive-images-with-hugo/
- original hugo figure shortcode from: https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/shortcodes/figure.html
- no unnecessary resizes and more nudges by Stéfan van der Walt https://mentat.za.net/
- mashing together and srcset logic fixes by Charl P. Botha https://cpbotha.net/
Changes:
- 2024-11-24 work-around for bug in golang where it breaks webp images during resizing. Please add png or jpg version of your webp.
- 2020-05-10 fall back to stock Hugo behaviour when no page bundle found
- 2020-05-04 no unnecessary resizes, sizes in array
- 2020-05-02 initial release
*/}}
{{/* hugo will resize to all of these sizes that are smaller than your original. configure if you like! */}}
{{ $sizes := (slice "480" "800" "1200" "1500") }}
{{/* get file that matches the filename as specified as src="" in shortcode */}}
{{ $src := .Page.Resources.GetMatch (printf "*%s*" (.Get "src")) }}
{{/* work-around for bug in golang where webp images are degraded during decoding / processing:
https://github.com/golang/go/issues/40173
in short: use .webp as src, but also add a png or jpg version. This code will link webp files everywhere,
but it will use the jpg/png as the image processing source */}}
{{ $srcIP := $src }}
{{ if and $src (strings.HasSuffix $src.Name ".webp") }}
{{/* use .jpg if that exists, else .png */}}
{{ $srcPNG := .Page.Resources.GetMatch (printf "*%s*" (replace (.Get "src") ".webp" ".png")) }}
{{ $srcJPG := .Page.Resources.GetMatch (printf "*%s*" (replace (.Get "src") ".webp" ".jpg")) }}
{{ if $srcPNG }}
{{ $srcIP = $srcPNG }}
{{ else if $srcJPG }}
{{ $srcIP = $srcJPG }}
{{ end }}
{{ end }}
<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>
{{- if .Get "link" -}}
<a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
{{- end }}
<img
{{ if $src }}
sizes="(min-width: 35em) 1200px, 100vw"
{{/* only srcset images smaller than or equal to the src (original) image size, as Hugo will upscale small images */}}
srcset='
{{ range $sizes }}
{{/* explicit MediaType.SubType to support our webp work-around without affecting non-webp use */}}
{{ if ge $src.Width . }}{{ ($srcIP.Resize (printf "%sx %s" . $src.MediaType.SubType)).Permalink }} {{ (printf "%sw" .) }},{{ end }}
{{ end }}'
{{/* when no support for srcset (old browsers, RSS), we load small (800px) */}}
{{/* if image smaller than 800, then load the image itself */}}
{{ if ge $src.Width "800" }}src="{{ ($srcIP.Resize (printf "800x %s" $src.MediaType.SubType)).Permalink }}"
{{ else }}src="{{ $src.Permalink }}"
{{ end }}
{{ else }}
{{/* fall back to stock hugo behaviour when image is not available in bundle */}}
src="{{ .Get "src" }}"
{{ end }}
{{- if or (.Get "alt") (.Get "caption") }}
alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
{{- end -}}
{{- with .Get "width" }} width="{{ . }}"{{ end -}}
{{- with .Get "height" }} height="{{ . }}"{{ end -}}
/> <!-- Closing img tag -->
{{- if .Get "link" }}</a>{{ end -}}
{{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
<figcaption>
{{ with (.Get "title") -}}
<h4>{{ . }}</h4>
{{- end -}}
{{- if or (.Get "caption") (.Get "attr") -}}<p>
{{- .Get "caption" | markdownify -}}
{{- with .Get "attrlink" }}
<a href="{{ . }}">
{{- end -}}
{{- .Get "attr" | markdownify -}}
{{- if .Get "attrlink" }}</a>{{ end }}</p>
{{- end }}
</figcaption>
{{- end }}
</figure>
@cpbotha
Copy link
Author

cpbotha commented Nov 25, 2024

I have just updated the shortcode to support working around this 4-year old webp decoding bug in the golang standard library: golang/go#40173

Here is more discussion on Hugo's github: gohugoio/hugo#8879

In short, the golang stdlib mangles webp images during decoding, which results in visible colour changes. I ran into this in a recent post with screenshots with white backgrounds, which turned into super grey.

With these changes in the shortcode, you can add e.g. "my_image.webp" and use that in the src and link attributes, but you must also add a "my_image.png" (or .png) version. The .png or .jpg will be used for all resizing, but your final page will link only the webp files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment