I’ve been thinking about adding some kind of photo implementation on this blog since I first set it up. I didn’t really know what I wanted, or how I wanted to use it - so I’ve been putting it off.
The last couple of weeks I’ve been more interested in photography, even borrowed a macro lens to see if that is something I enjoyed (it was 👍)
It’s time to tackle the photo implementation!
Table of contents
Single photo, or gallery posts?
I gave a lot of thought to whether the photos should have their own post, like BrianLi is doing, or be part of a gallery.
Single photo post
Initially I decided that one photo per post was the most flexible, as I could freely organize them into one or more taxonomies. A photo could be part of one gallery, many galleries, or no galleries.
I thought about the URL structure, and eventually decided that each photo would get a unique ID instead of using slugs. That would make my photo URLs something lie /photos/fjh43g/
, and galleries /galleries/plants/
.
But once I actually started using it — I found that it was difficult to mange, even with the helper script I had made.
If I added 20 photos, 20 posts were created. If I wanted to add or alter the taxonomies for those photos, I had to edit 20 index.md
files. In my RSS feed; 20 new “posts”.
I abandoned the “one-photo-one-post” approach and tried making gallery posts instead.
Photo gallery post
The more I thought about the concept of “photo gallery posts” — the more sense it started to make. I rarely take just one photo, so each gallery post would be a “photo shoot”.
And it is more manageable 😃 Instead of 20 posts, I will have one post, with 20 photos. For taxonomies I can use tags, or series, or something else — I haven’t decided yet. It’s easier to make those decisions later, when I’ve used it for a while.
Naming things is hard
I did think way too much about what to call such a post — photo post, gallery post, album post? Bike-shedding for sure. In the end I decided to not call it anything. It’s a post. The underlying type is not really important. I did add a camera icon, indicating that the post is photo related.
Implementation
To make the galleries, I’m only using CSS Flexbox. For showing individual photos, and its title and metadata, I am using lightgallery.js.
Here is the part of the layout that creates the gallery:
<div class="photo-container" id="lightgallery">
{{- $context := . -}}
{{- range $src := .Page.Resources.Match "gallery/**.jpg" -}}
{{- $title := (replace .Title "gallery/" "") -}}
{{- $exifJson := $context.Page.Resources.GetMatch (printf "%s.json" .Name) -}}
{{- $exif := slice -}}
{{- with $exifJson -}}
{{- with (index (.Content | unmarshal) 0) -}}
{{- with .Title -}}{{- $title = . -}}{{- end -}}
{{- with .Make2 -}}{{- $exif = $exif | append (printf "Make: %s" .) -}}{{- end -}}
{{- with .Model -}}{{- $exif = $exif | append (printf "Camera: %s" .) -}}{{- end -}}
{{- with .LensSpec -}}{{- $exif = $exif | append (printf "Lens: %s" .) -}}{{- end -}}
{{- with .FocalLength -}}{{- $exif = $exif | append (printf "Focal length: %s" .) -}}{{- end -}}
{{- with .FNumber -}}{{- $exif = $exif | append (printf "Aperture: ƒ/%.1f" .) -}}{{- end -}}
{{- with .ExposureTime -}}
{{- if eq (printf "%T" .) "float64" -}}
{{- $exif = $exif | append (printf "Exposure time: %.1f s" .) -}}
{{- else -}}
{{- $exif = $exif | append (printf "Exposure time: %s s" .) -}}
{{- end -}}
{{- end -}}
{{- with .ISO -}}{{- $exif = $exif | append (printf "ISO: %.0f" .) -}}{{- end -}}
{{- end -}}
{{- end -}}
<div class="photo-item" data-src="{{ .Permalink }}" data-sub-html="<h4>{{ $title }}</h4><p>{{ delimit $exif " | " }}</p>">
{{- $crop := default "smart" -}}
{{- $tinyw := printf "500x375 %s Lanczos q85" $crop -}}
{{- $smallw := printf "800x600 %s Lanczos q80" $crop -}}
{{- $mediumw := printf "1200x900 %s Lanczos q40" $crop -}}
{{- $largew := printf "1600x1200 %s Lanczos q30" $crop -}}
{{- $srcset := slice -}}
{{- $tiny := ($src.Fill $tinyw) -}}
{{- $srcset = $srcset | append (printf "%s 500w" $tiny.Permalink) -}}
{{- $img := dict "src" $tiny.RelPermalink "w" $tiny.Width "h" $src.Height -}}
{{- if and (ge $src.Width "800") (ne $src.MediaType.SubType "png") -}}
{{- $small := ($src.Fill $smallw) -}}
{{- $srcset = $srcset | append (printf "%s 800w" $small.Permalink) -}}
{{- $img = dict "src" $small.RelPermalink "w" $small.Width "h" $small.Height -}}
{{- end -}}
{{- if and (ge $src.Width "1200") (ne $src.MediaType.SubType "png") -}}
{{- $medium := ($src.Fill $mediumw) -}}
{{- $srcset = $srcset | append (printf "%s 1200w" $medium.Permalink) -}}
{{- end -}}
{{- if and (ge $src.Width "1600") (ne $src.MediaType.SubType "png") -}}
{{- $large := ($src.Fill $largew) -}}
{{- $srcset = $srcset | append (printf "%s 1600w" $large.Permalink) -}}
{{- end -}}
{{- $sizes := "(min-width: 900px) 420px, (min-width: 684px) 310px, calc(100vw - 40px)" -}}
<a href="{{ .Permalink }}">
<picture>
<source type="{{ $src.MediaType }}" sizes="{{ $sizes }}" srcset='{{ delimit $srcset ", " }}'>
<img loading="lazy" class="center"
src="{{ $img.src }}" width="{{ $img.w }}" height="{{ $img.h }}" alt="{{ $title }}">
</picture>
</a>
</div>
{{ end }}
</div>
It looks for page resources matching gallery/**.jpg
, each photo is expected to have a json file with metadata at <filename>.jpg.json
. Multiple sizes of the photo is created, and added to the image source set.
EXIF data is added to the $exif
array, and shown below the photo when opened in the lightbox. If the EXIF data contains a title tag, this is used, otherwise the page resource title.
A bit of (S)CSS is also needed:
.photo-container {
display: flex;
flex-wrap: wrap;
margin: 0 -40px;
@media(max-width:899px) {
margin: 0;
}
img {
border-radius: 8px;
}
.photo-item {
padding: 2px;
width: 50%;
@media(max-width:683px) {
width: 100%;
}
}
}
To conditionally load the lightgallery.js library, I have the following code in the head
partial:
{{- if and (eq .Type "photos") (eq .Kind "page") -}}
<link rel="stylesheet" href="{{ (resources.Get "assets/lightgallery.min.css" | fingerprint).Permalink }}" />
{{- end -}}
And in the footer
:
{{- if and (eq .Type "photos") (eq .Kind "page") -}}
<script src="{{ (resources.Get "assets/lightgallery.min.js" | fingerprint).Permalink }}"></script>
<script>lightGallery(document.getElementById('lightgallery'), {selector: '.photo-item'});</script>
{{- end -}}
This is the command I use to save EXIF metadata to json files:
exiftool ${@:1} -w %f.%e.json -json -struct \
-EXIF:All \
-XMP:Title \
-Composite:LensSpec
I think I struck a nice balance between simplicity and functionality. See for yourself — here is my first photo gallery post 😃
Last commit 2024-11-11, with message: Add lots of tags to posts.