Recently I decided that I want a photo gallery on my website, a static HTML site generated by Jekyll, just like this one. I wanted a page that lists all the albums, an index for each album that shows a gallery of all the photos, which lead to slideshow of larger images that then lead to the full raw images themselves. It turned into a fun exercise of writing Jekyll front matter and templates inside Ruby strings in a Rake task, trying to keep variables straight where data flows from one process to the next.
I wanted the “input” to be as simple as possible: a directory of the images I wanted to add as an album. I decided at the beginning that I’d like all metadata about each image, such as time, location and descriptions, to reside in the image file itself as EXIF data, to ensure maximum portability and consistency. The directory name would be a slugified version of the album’s name and date as an album ID:
photos/ 2017-10-15-my-album-name/ image_1.jpg image_2.jpg ... 2017-11-1-another-album-name/ image_1.jpg image_2.jpg ...
I began designing a Rake task to extract the descriptions and other EXIF data from the images and create HTML files containing the necessary YAML front matter to access in Liquid templates. Originally I thought the preprocessing should generate a directory structure like this:
_photos/ index.html 2017-10-15-my-album-name/ index.html img/ image_1.html image_1.jpg image_2.html image_2.jpg ... 2017-11-1-another-album-name/ index.html img/ image_1.html image_1.jpg image_2.html image_2.jpg ...
where each album’s
image_N.html would contain YAML front matter for the album and individual images, respectively. Then I’d have a single collection specified in my
collections: - photos
and have Jekyll treat each subdirectory as a collection item, containing another sort of collection in each
img/ directory. I hoped it could somehow work automatically, instead of having to dynamically insert each album ID in the
collections: - photos - 2017-10-15-my-album-name - 2017-11-1-another-album-name - ...
which would certainly be an abuse of
Unfortunately, this is not the way Jekyll collections are designed to work. They must all be in the same directory (the root directory by default, but recently support for a common subdirectory was added; unfortunately this model remains too simple to implement more complicated logic and structures, as mentioned in this issue, for example). So, no subcollections, or whatever I was trying to do with
The final design has two collections:
collections: - photos - albums
I still use as “input” to the Rake task a directory containing images, under the main
photos directory (different from the
photos/ 2017-10-15-my-album-name/ image_1.jpg image_2.jpg ...
This directory path is the sole argument to the Rake task, which carries out the following steps:
_photos/2017-10-15-my-album-name-N.html, using data pulled from each image using exiftool (where N is the sequential order of the photos by time and date, sorted in the Rake task)
The steps are designed to be as idempotent as possible, so it will e.g. read the album name, description and cover image filename from a preexisting album HTML’s YAML, or skip thumbnail generation if they already exist. After running the Rake task for the example input above, we should have the following files:
_photos/ 2017-10-15-my-album-name-1.html 2017-10-15-my-album-name-2.html _albums/ 2017-10-15-my-album-name.html photos/ 2017-10-15-my-album-name/ slideshow/ index.html img/ image_1.jpg image_1-thumbnail.jpg image_2.jpg image_2-thumbnail.jpg
When the Jekyll build is run, each photo gallery index template will filter through all albums’ photos, picking out only the ones that specify the same album ID. Jekyll collections operate very similarly to MapReduce, where you have huge flat lists of items that you can filter, group and map by relations at runtime. The resulting output in
_site/ looks like this:
_site/ photos/ index.html (album list) 2017-10-15-my-album-name/ index.html (this album's thumbnail gallery) slideshow/ index.html (first big image in slideshow) 2/ index.html (next big image in slideshow) img/ image_1.jpg image_1-thumbnail.jpg image_2.jpg image_2-thumbnail.jpg
One thing I haven’t quite figured out is how to force
octopress-paginate to put the first slide under a subdirectory named
1/, so I could have consistent navigation semantics for navigating back from the slideshow. I have an
_include that constructs a “Back” button for any page, so I simply parameterized it to remain as-is (
<a href="..">...) for the first slide, since that would pop back to the thumbnail gallery, and override it to point at
"../.." for all the subdirectory slides.
Originally I built a vertical list, with thumbnails on the left and descriptions on the right, extending all the way across the page (this is the layout used for the album list). This left too much negative space on the right, so I decided to go with a more grid-like layout, flowing from left to right and wrapping to the window width. Each cell would have an image with its description underneath, where the description wraps to the width of the image–this turns out to be tricky using HTML/CSS. You need to specify a width for the table for
word-wrap: break-word; to work in the cell containing the description. So, I had to read the image width using
exiftool, write it to the photo’s YAML front matter, then retrieve that value in the liquid tags that render the gallery HTML. In the end it’s an interesting mix of interpolating Ruby variables into shell invocations, Liquid templates and YAML front matter, and programming the use of front matter variables from Liquid templates within Ruby strings.
Originally each image thumbnail linked directly to the full-size raw image. I wanted it instead to enter a slideshow to page back and forth through larger images, again with their descriptions. Each image here would then be clickable to get the raw version, to zoom using the browser’s built in tools or save. I added a new step in the Rake task to create
photos/$albumName/slideshow/index.html, and inject YAML front matter utilizing octopress-paginate to generate a slideshow page for each image. To do so, I simply set the
per_page value to
1 and the collection to paginate as
This worked great for the first photo gallery I tested with, but once I added the second, a problem surfaced: each album’s slideshow was paginating through all of the photos in
_photos, irrespective of album. The final touch was to write a
category parameter into the front matter of each HTML file in
_photos that matches the album ID, and then paginate on both the
photos collection and the
category matching the target album for the slideshow. (Another nit with
octopress-paginate is that specifying both
category outputs scary deprecation notices-which seems to grow w.r.t. the product of the amount of collections and categories-but does not break the build: GitHub issue. Beware that
octopress-paginate does not appear to be actively maintained–the last commit was over 13 months ago now, and that GitHub issue is about the same age.)