#Site Updates #Eleventy #Web Development
Adding Image Galleries to My Website
At last, I’ve gotten around to implementing image galleries.
I’ve been meaning to add an image gallery feature to this website for ages, and I’m happy to finally announce that I’ve done it! If you’d like to see my very first image gallery in action, here’s a gallery of PopTart memes I’ve collected. If you’re a massive nerd and would like to read about how I implemented this feature on my Eleventy website, read on.
Note: this is a fast and loose description of the process. It should be helpful if you’re trying to do this yourself, but don’t expect to be able to copy and paste my implementation. See the repo to copy and paste my spaghetti.
Specifying new galleries #
First of all, I had to decide how new galleries would be specified
so that Eleventy could work its magic. There are a few approaches
here, such as utilizing template frontmatter, directories of images,
or using a data file in my site’s _data directory.
After weighing up the pros and cons, I decided to use the latter
option, even though it’s not the most ergonomic. So I created
_data/galleries.js, which looks like this[1]:
export default [
{
title: "",
description: "",
synopsis: "",
url: "",
date: new Date(""),
galleryImage: "",
galleryImageAlt: "",
pictures: [
{
title: "",
filename: "",
altText: "",
thumbAltText: "",
caption: "",
},
],
},
];
Gallery index pagination #
Now that I know how I’ll go about specifying my galleries, I need to
paginate the gallery index pages; the pages that feature thumbnails
of each image in the gallery and allow the user to navigate to the
images. To do this, I created content/galleries.njk:
---
pagination:
data: galleries
size: 1
alias: gallery
layout: layouts/base.njk
tags: gallery
eleventyComputed:
title: "{{ gallery.title }}"
permalink: "/gallery/{{ gallery.title | slugify }}/"
description: "{{ gallery.description }}"
---
<h1>{{ gallery.title }}</h1>
<p>{{ gallery.description }}</p>
<section>
{% for picture in gallery.pictures %}
<a href="/gallery/{{ gallery.title | slugify }}/{{ picture.filename | slugify }}/">
<div>
<img alt="{{ gallery.thumbAltText }}" class="gallery-image" src="{{ gallery.url }}{{ picture.filename }}">
</div>
</a>
{% endfor %}
</section>
By ensuring that each gallery index page is tagged “gallery,” they will automatically be grouped into a new collection by that name, which is important so that I can filter galleries out from post lists and other things throughout my site.
Generating image pages #
I considered using a lightbox / modal sort of arrangement for viewing images, but I decided against it as it can be tricky to get accessibility right for these patterns, and I wanted to avoid using JavaScript if possible. What I chose to do instead was to create a page for each image, which would feature buttons to navigate to the next / previous image, and to return to the gallery. The first step was to create a collection containing all gallery images in my Eleventy config file:
// Collections
eleventyConfig.addCollection("galleryImages", (collection) => {
const galleries = collection.getAll()[0].data.galleries;
let galleryImages = [];
galleries.forEach((gallery) => {
gallery.pictures.forEach((picture, i, arr) => {
picture.containingGallery = `${gallery.title}`;
picture.baseUrl = `${gallery.url}`;
i ? (picture.previousImage = arr[i - 1].filename) : null;
i + 1 != arr.length ? (picture.nextImage = arr[i + 1].filename) : null;
galleryImages.push(picture);
});
});
You may notice that I did a little jiggery-pokery in that callback
function to provide information about each image that isn’t included
in _data/galleries.js because it would have been a pain
to include or would have unecessarily inflated the tile size,
specifically: the filenames of images before and after the current
image (should they exist), and the URL of the gallery that the image
belongs to. We can use this information to have our image pages
generated in the same directory as their parent gallery index, and
to generate next / previous buttons to help the user navigate
through the gallery. This information is added to the
galleryImages collection object in memory so that we
can use it when we paginate the image pages using
content/galleryImage.njk:
---
pagination:
data: collections.galleryImages
size: 1
alias: picture
layout: layouts/base.njk
eleventyComputed:
title: "Image: {{ picture.title }}"
permalink: "/gallery/{{ picture.containingGallery | slugify }}/{{ picture.filename | slugify }}/"
description: "{{ picture.title }} from gallery: {{ picture.containingGallery}}"
---
<article>
<h1>{{ picture.title }}</h1>
<div>
{% if picture.previousImage %}
<a href="../{{ picture.previousImage | slugify }}">
<button type="button">Previous</button>
</a>
{% endif %}
<a href="/gallery/{{ picture.containingGallery | slugify }}/">
<button type="button">Gallery</button>
</a>
{% if picture.nextImage %}
<a href="../{{ picture.nextImage | slugify }}">
<button type="button">Next</button>
</a>
{% endif %}
</div>
<figure>
<a href="{{ picture.baseUrl }}/{{ picture.filename }}">
<img src="{{ picture.baseUrl }}/{{ picture.filename }}" alt="{{ picture.altText }}">
</a>
{% if picture.caption %}
<figcaption>
{{ picture.caption }}
</figcaption>
{% endif %}
</figure>
</article>
Et voilà; we have galleries! But before we can call this project
done, it would be nice to have a page that lists all galleries on
the site for the benefit of visitors. For this, I created
/content/galleries/index.njk:
---
layout: layouts/base.njk
eleventyNavigation:
key: Pics
order: 4
---
<h1>Image Galleries</h1>
<p>
Some pictures I thought would be worth posting.
</p>
<section>
<div>
{% for gallery in galleries %}
<article>
<a href="../gallery/{{ gallery.title | slugify }}" class="postlist-link">
<div>
<img {% if gallery.galleryImage %} src="{{ gallery.url }}{{ gallery.galleryImage }}" alt="{{ gallery.galleryImageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
</div>
</a>
<div>
<a href="../gallery/{{ gallery.title | slugify }}" class="postlist-link">
<h3>
{{ gallery.title }}
</h3>
</a>
<time datetime="{{ gallery.date | htmlDateString}}">{{ gallery.date | readableDate("LLLL yyyy") }}</time>
<p>{{ gallery.synopsis | truncate(105) | safe }}</p>
</div>
</article>
{% endfor %}
</div>
</section>
Note that readableDate() and
htmlDateString are custom filters that came with the
Eleventy Base Blog template
that I based my website on. They require Luxon:
eleventyConfig.addFilter("readableDate", (dateObj, format, zone) => {
// Formatting tokens for Luxon: https://moment.github.io/luxon/#/formatting?id=table-of-tokens
return DateTime.fromJSDate(dateObj, { zone: zone || "utc" }).toFormat(
format || "dd LLLL yyyy",
);
});
eleventyConfig.addFilter("htmlDateString", (dateObj) => {
// dateObj input: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat("yyyy-LL-dd");
});
You may be thinking that this all seems a convoluted, and I agree! If you know of a simpler way to accomplish this functionality, do feel free to let me know, but for now, I’m just happy that I finally have the ability to add image galleries to this website.
-
Thanks to those who answered my cry for help! ↩︎