Adding a picture gallery to your Hugo blog

The Hugo blog engine is quite amazing and extremely powerful.

Basic markdown allows embedding images like this:

1
![Alt Text](Image URL)

This is enough to get started, but once you add more images to a web page, you want some optimizations for it. The first step is to provide different resolutions for each picture, so the browser can always pick the one that is appropriate for the viewing dimensions (responsive images), which saves bandwidth resources and time. Using this excellent post about Image Processing with Hugo from Laura Kalbag, I was able to set up a Hugo Shortcode that will generate multiple versions of an image and automatically link to all of them in the resulting HTML (with srcset attribute):

Input Hugo Markdown:

{{< img src="salatini.jpg" alt="Salatini" >}}

Output HTML:

1
2
3
4
5
6
7
8
9
<img
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
      /post/salatini/salatini_hu59f9cfc1f370ff38e37bafd759b34267_2439889_500x0_resize_q75_box.jpg 500w,
      /post/salatini/salatini_hu59f9cfc1f370ff38e37bafd759b34267_2439889_800x0_resize_q75_box.jpg 800w,
      /post/salatini/salatini_hu59f9cfc1f370ff38e37bafd759b34267_2439889_1200x0_resize_q75_box.jpg 1200w,
      /post/salatini/salatini_hu59f9cfc1f370ff38e37bafd759b34267_2439889_1500x0_resize_q75_box.jpg 1500w'
  src="http://localhost:1313/post/salatini/salatini.jpg"
  alt="Salatini">

When there are multiple pictures on the same page, I want to consolidate them into one picture gallery.

The first step for this was to find a nice HTML/CSS/(JS) bundle that would take of the gallery effect. The first interesting option I found was Gallery.css from Ben Schwarz. It is simple and even works without JavaScript. I set up a basic demo like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<div class="gallery items-2">
  <div id="item-1" class="control-operator"></div>
  <div id="item-2" class="control-operator"></div>

  <figure class="item">
  ![pasta](./pasta1.jpg)
  *Pasta with zucchini, potatoes and sausage*
  </figure>

  <figure class="item">
  ![pasta](./pasta2.jpg)
  *Pasta with zucchini, potatoes and sausage*
  </figure>

  <div class="controls">
    <a href="#item-1" class="control-button">•</a>
    <a href="#item-2" class="control-button">•</a>
  </div>
</div>

Super clean and easy to use. At first I though it was broken, since it didn’t seem like the gallery was working:

Gallery.css Demo Screenshot

Turns out, the buttons were just really hard to see. (can you see the tow round circles at the bottom of the picture?)

While it’s super nice that it only uses HTML/CSS and no JavaScript, it’s really more of a slideshow and less what I understand as a “gallery”. In particular, it was missing the fullscreen viewing mode.


Instead, I opted for lightGallery.js, a simple-to-use yet full-feature JavaScript library for galleries.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div id="gallery">
  <a href="pasta1.jpg" data-sub-html=".caption">
    <img class="img-responsive" src="pasta1.jpg" />
      <div class="caption">
        <h4>Caption 1</h4>
        <p>Description 1</p>
      </div>
  </a>
  <a href="pasta2.jpg">
    <img src="pasta2.jpg" />
      <div class="caption">
        <h4>Caption 2</h4>
        <p>Description 2</p>
      </div>
  </a>
</div>

This worked fine for the fullscreen view of the gallery, however the individual pictures were still just … pictures on the web page (without any frame or other markup around it). To fix this, I added some more HTML and CSS, to make it look more like a collection of pictures (gallery) and fit in with the rest of the blog style.

lightGallery.js Demo Screenshot

Looks good!

What I also like about the HTML above is that even when someone has JavaScript disabled, the detailed view works, since as a fallback the picture (<img>) is wrapped in a link (<a>) to the full resolution picture.

To make it more obvious that clicking / tapping on these images opens a detailed view, I wanted to add a little searchglass icon. I used the “zoom-in” icon from Heroicons. By the way: Heroicons is extremely simple to use. Just search for an icon you like, click on it and the HTML SVG code is copied to your clipboard, available for you to insert it into your web page.

Doing this I learned that there is a neat trick with SVG images in HTML. While you can just directly embed them into your HTML everywhere you need them, this is not DRY. Instead, you can use the href attribute of the <use> tag to refer to a symbol definition somewhere else (URL), even just in your own web page (with HTML #anchors).

https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use

https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/href

In the following example I defined the zoom-in symbol just once (at the end), and then referred to that definition two times (line 9 and 23).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<ul id="gallery" class="posts aside" style="list-style: none;">

  <li class="post_item" data-src="./pasta1.jpg" data-sub-html=".caption">
    <a class="post_card" href="./pasta1.jpg" title="Caption 1" style="background-image: url(pasta1.jpg);"></a>
    <div class="excerpt">
      <div class="excerpt_meta">
        <div class="copy" data-share="Open Fullscreen">
          <svg>
            <use href="#zoom-in"></use>
          </svg>
        </div>
      </div>
    </div>
    <p class="pale caption">Caption 1</p>
  </li>

  <li class="post_item" data-src="./pasta2.jpg">
    <a class="post_card" href="./pasta2.jpg" title="Caption 2" style="background-image: url(pasta2.jpg);"></a>
    <div class="excerpt">
      <div class="excerpt_meta">
        <div class="copy" data-share="Open Fullscreen">
          <svg>
            <use href="#zoom-in"></use>
          </svg>
        </div>
      </div>
    </div>
    <p class="pale caption">Caption 2</p>
  </li>

</ul>

<script src="/js/lightgallery.js"></script>
<script>
  lightGallery(document.getElementById('gallery'), {
  /* options: https://sachinchoolur.github.io/lightgallery.js/docs/api.html#options */
  });
</script>

<svg width="0" height="0" class="hidden">
  <symbol viewBox="0 0 20 20" id="zoom-in">
    <path d="M5 8a1 1 0 011-1h1V6a1 1 0 012 0v1h1a1 1 0 110 2H9v1a1 1 0 11-2 0V9H6a1 1 0 01-1-1z"></path><path fill-rule="evenodd" d="M2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8zm6-4a4 4 0 100 8 4 4 0 000-8z" clip-rule="evenodd">
    </path>
  </symbol>
</svg>

That should be enough for now. Next step: wrap it all up in a Hugo Shortcode for easy re-use! To be precise, two new shortcodes: one for the gallery itself and the other one for the items within the gallery.

{{< gallery >}}
  {{< figure img="pasta1.jpg" caption="Caption 1" >}}
  {{< figure img="pasta2.jpg" caption="Caption 2" >}}
{{< /gallery >}}

The gallery shortcode creates the outer HTML elements for the gallery and most importantly loads the lightGallery.js library and initializes it.

layouts/shortcodes/gallery.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<ul id="gallery" class="posts aside" style="list-style: none;">
  {{/* content of the gallery is specified by figures */}}
  {{ .Inner }}
</ul>

<script src="/js/lightgallery.js"></script>
<script>
    lightGallery(document.getElementById('gallery'), {
        /* options: https://sachinchoolur.github.io/lightgallery.js/docs/api.html#options */
    });
</script>

The definition of the SVG Icon could be added in the above shortcode snippet, however the theme used for the blog (hugo-swift-theme) already has a partial HTML template where several other SVG icons are defined, so I used that instead.

layouts/shortcodes/figure.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{{/* shortcode for generating images in a gallery */}}
{{/* parameters: img, caption */}}

{{/* get file reference */}}
{{ $src := .Page.Resources.GetMatch (printf "*%s*" (.Get "img")) }}

{{/* resize it for a preview */}}
{{ $thumbnail := ($src.Fill "320x160") }}

{{/* resize/optimize large version */}}
{{ $large := ($src.Resize "1500x q90") }}

<li class="post_item" data-src='{{ $large.RelPermalink }}' data-sub-html=".caption">
  <a class="post_card" href='{{ $large.RelPermalink }}' title='{{ (.Get "caption") }}' style='background-image: url({{ $thumbnail.RelPermalink }});'></a>
  <div class="excerpt">
    <div class="excerpt_meta">
      <div class="copy" data-share="Open Fullscreen">
        <svg>
          <use href="#zoom-in"></use>
        </svg>
      </div>
    </div>
  </div>
  <p class="pale caption">{{ (.Get "caption") }}</p>
</li>

The shortcode first retrieves the Resource object for the specified image (img, line 5). It then creates two versions of the picture: a small thumbnail (Hugo defaults to 75% JPEG compression) and a large version for the fullscreen view (with a maximum width of 1500 pixels and 90% quality). The links (.RelPermalink) to these generated resources - in this case pictures - are then inserted into the HTML at the appropriate positions, alongside the specified caption.

You can see the gallery in action here.

Overall, adding this little gallery to Adele’s blog was quite an enjoyable learning experience. It took me a few hours to collect all the breadcrumbs, but picking up of each of them was easy enough and didn’t involve any magic.