Skip to content

Commit d9a5821

Browse files
committed
dynamic-images: Add post
1 parent 6de4085 commit d9a5821

File tree

5 files changed

+147
-0
lines changed

5 files changed

+147
-0
lines changed

content/posts/dynamic-images.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
title: "Dynamic images in Hugo"
3+
date: 2025-11-25T13:18:31+00:00
4+
tags:
5+
- Hugo
6+
- HTML
7+
- CSS
8+
- Excalidraw
9+
showToc: true
10+
TocOpen: false
11+
---
12+
13+
{{<
14+
dynamic-image
15+
light="/img/dynamic-image-hugo-light.svg"
16+
dark="/img/dynamic-image-hugo-dark.svg"
17+
alt="Excalidraw'd Hugo logo"
18+
>}}
19+
20+
As you may have noticed, this website can be displayed in either light or dark
21+
mode (toggle with sun/moon top left of page or with `Alt+T`).
22+
23+
An issue is that images and diagrams are perfectly visible in light mode, but
24+
when flipping over to dark mode they either are surrounded by a big white box
25+
or lost all contrast if the background is transparent, or vice versa.
26+
27+
I needed dynamic images; selective images depending on what mode is enabled.
28+
29+
## The `dynamic-image` shortcode
30+
31+
In [Hugo](https://gohugo.io/)—the tool used to create this website—you can
32+
extend the markdown by invoking templates called shortcodes. This is a short
33+
code I managed to hack together with help some LLM[^1].
34+
35+
[^1]: I found similar another blog solving the same problem in another way as well.
36+
https://stenbrinke.nl/blog/adding-support-for-dark-and-light-images-to-hugo-figure-shortcode/
37+
38+
[`layouts/shortcodes/dynamic-image.html`](https://github.com/Granddave/site/blob/108d17157135248445dc8917afd8ddb503ee8317/layouts/shortcodes/dynamic-image.html):
39+
40+
```html
41+
{{ $lightSrc := .Get "light"}}
42+
{{ $darkSrc := .Get "dark"}}
43+
{{ $alt := .Get "alt" }}
44+
{{ $class := .Get "class" }}
45+
{{ $style := .Get "style" }}
46+
47+
<div class="dynamic-image {{ $class }}" style="{{ $style }}">
48+
<center>
49+
<img src="{{ $lightSrc }}" alt="{{ $alt }}" class="light-mode-img">
50+
<img src="{{ $darkSrc }}" alt="{{ $alt }}" class="dark-mode-img">
51+
</center>
52+
</div>
53+
```
54+
55+
And the accompanying CSS...
56+
57+
[`assets/css/extended/dynamic-image.css`](https://github.com/Granddave/site/blob/108d17157135248445dc8917afd8ddb503ee8317/assets/css/extended/dynamic-image.css):
58+
```css
59+
.dynamic-image img {
60+
display: none;
61+
}
62+
body:not(.dark) .dynamic-image .light-mode-img {
63+
display: block;
64+
}
65+
body.dark .dynamic-image .dark-mode-img {
66+
display: block;
67+
}
68+
```
69+
70+
Effectively what we've done here is to make sure to only display the "dark"
71+
variant when dark-mode is enabled (`body.dark`) and the "light" variant when light-mode is
72+
enabled (`body:not(.dark)`).
73+
74+
Below is an example usage taken from my last post about [reverse
75+
proxies](posts/reverse-proxy/):
76+
77+
```html
78+
{{</*
79+
dynamic-image
80+
light="/img/reverse-proxy-without-light.svg"
81+
dark="/img/reverse-proxy-without-dark.svg"
82+
alt="Without a reverse proxy"
83+
*/>}}
84+
```
85+
{{<
86+
dynamic-image
87+
light="/img/reverse-proxy-without-light.svg"
88+
dark="/img/reverse-proxy-without-dark.svg"
89+
alt="Without a reverse proxy"
90+
>}}
91+
92+
Now if you toggle the light/dark mode you should see that the text, arrows and
93+
colors are visible in both modes.
94+
95+
Below is an animation toggling the images back regardless of what the current
96+
mode is and forth to illustrate the effect.
97+
98+
{{<
99+
imagetoggle
100+
img1="/img/reverse-proxy-without-light.svg"
101+
img2="/img/reverse-proxy-without-dark.svg"
102+
>}}
103+
104+
A downside to this approach is that different "web readers" strip CSS making the
105+
images disappear or sometimes even show both variants. Maybe there is some way
106+
to detect in the shortcode..?
107+
108+
---
109+
110+
## Diagram generation
111+
112+
I might as well mention how these diagrams are made.
113+
I use a web application called [Excalidraw](https://excalidraw.com/), which is
114+
a drawing application that stores all data locally and is great for quick
115+
diagrams and illustrations. The projects can be downloaded to `.excalidraw` files
116+
and the full canvas or selected elements can be exported as images (PNG and SVG).
117+
118+
The trick here is to export the same image twice, once in dark mode and once in
119+
light mode.
120+
121+
During export, make sure *Background* is unchecked, and then export the image
122+
as SVG. Export the image once again but now toggle the *Dark mode* option.
123+
124+
{{< figure src="/img/dynamic-images-export.png" >}}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{{ $interval := .Get "interval" | default "1000" }}
2+
3+
<center><img id="toggle-img-{{ .Get "id" }}" src="{{ .Get "img1" }}" alt="toggle"></center>
4+
5+
<script>
6+
(function(){
7+
const images = ["{{ .Get "img1" }}", "{{ .Get "img2" }}"];
8+
let index = 0;
9+
const imgElem = document.getElementById("toggle-img-{{ .Get "id" }}");
10+
setInterval(() => {
11+
index = (index + 1) % images.length;
12+
imgElem.src = images[index];
13+
}, {{ $interval }});
14+
})();
15+
</script>
Lines changed: 4 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)