Go back to the homepage

#1: Creating image galleries in eleventy(11ty) with elventy-img

I wanted to have image galleries on my site and Eleventy didn't have any plugins that did this already. So I added support for it on my own.

I seem to have a weird obssession with recreating my blog. The site you are looking at now is the eight iteration and after using Wordpress templates and Gatsby starters for all the previous iterations, I decided to design and develop this version from scratch so that I would have total control over how it looks liked and worked.

After considering several static site generators, I went with eleventy because it wasn't very opinionated, didn't use clientside Javascript and was built with NodeJS where I can debug and fix any issues on my own. Since I also posted my photos on this site, I wanted to be able to create image galleries in the posts I made. Since I couldn't find a built in plugin or module that did this for me, I set out to create one on my own. This is what I ended up with.

Gallery built with eleventy-img

To build this gallery, I used eleventy-img, photoswipe and shortcodes. eleventy-img is an official plugin by the 11ty community for doing image transformation. photoswipe is the best looking image lightbox I have seen. and shortcodes allow the templating engines to be extended with custom macros that resolve to custom HTML. While I could have used a full blown masonry library for the visuals of the gallery, I decided to go with a CSS only option using the CSS grid layout. The main problem here was that images in my galleries could be both portrait and landscape and a simple grid implementation would not display this nicely since it would use the same size for all its elements. But I found this helpful codepen which proposed a workaround with the only downside being the images would be displayed from top to bottom rather than from left to right. You can find all the snippets below and more on github

So here is how it all comes together. I write my post in a markdown file as usual and use the custom "gallery" and "galleryImage" shortcodes.

{% gallery "uzupis" %}
{% galleryImage "posts/story/uzupis/1-tibeto-skveras.jpg", "Tiberto Skveras" %}
{% galleryImage "posts/story/uzupis/2-tibetan-flags.jpg", "Tibetan Flags" %}
{% endgallery %}

These shortcodes are then expanded by eleventy when it processes the markdown to generate the gallery HTML.

gallery shortcode implementation

// .eleventy.js
function galleryShortcode(content, name) {
return `
<div>
<div class="gallery" id="gallery-
${name}">
${content}
</div>
<script type="module">
import PhotoSwipeLightbox from '/js/photoswipe-lightbox.esm.min.js';
import PhotoSwipe from '/js/photoswipe.esm.min.js';
const lightbox = new PhotoSwipeLightbox({
gallery: '#gallery-
${name}',
children: 'a',
pswpModule: PhotoSwipe,
preload: [1, 1]
});
lightbox.init();
</script>
</div>
`
.replace(/(\r\n|\n|\r)/gm, "");
}

module.exports = function(eleventyConfig) {
...
eleventyConfig.addPairedLiquidShortcode('gallery', galleryShortcode)
...
}

The gallery shortcode is a paired shortcode which allows elements placed within the start {% gallery "uzupis" %} and end {% endgallery %} block to be passed in to the shortcode handler function. It also initializes the photoswipe library and creates the container element expected by it.

galleryImage shortcode implementation

// .eleventy.js
const sharp = require('sharp');
const Image = require('@11ty/eleventy-img');

const GALLERY_IMAGE_WIDTH = 192;
const LANDSCAPE_LIGHTBOX_IMAGE_WIDTH = 2000;
const PORTRAIT_LIGHTBOX_IMAGE_WIDTH = 720;

async function galleryImageShortcode(src, alt) {
let lightboxImageWidth = LANDSCAPE_LIGHTBOX_IMAGE_WIDTH;

const metadata = await sharp(src).metadata();

if(metadata.height > metadata.width) {
lightboxImageWidth = PORTRAIT_LIGHTBOX_IMAGE_WIDTH;
}

const options = {
formats: ['jpeg'],
widths: [GALLERY_IMAGE_WIDTH, lightboxImageWidth],
urlPath: "/gen/",
outputDir: './_site/gen/'
}

const genMetadata = await Image(src, options);

return `
<a href="
${genMetadata.jpeg[1].url}"
data-pswp-width="
${genMetadata.jpeg[1].width}"
data-pswp-height="
${genMetadata.jpeg[1].height}"
target="_blank">
<img src="
${genMetadata.jpeg[0].url}" alt="${alt}" />
</a>
`
.replace(/(\r\n|\n|\r)/gm, "");;
}

module.exports = function(eleventyConfig) {
...
eleventyConfig.addLiquidShortcode('galleryImage', galleryImageShortcode)
...
}

The galleryImage shortcode uses eleventy-img to take the full size image referenced by the URL and generate the thumbnail image and images for the fullscreen lightbox. Next it generates the markup expected by photoswipe. Since photoswipe requires the dimensions of each image to be explictly specified, this info is taken from the metadata returned by eleventy-img and injected into the markup.

This is also where I ran into a bug with eleventy-img where it wouldn't handle rotated images properly and would generate flipped images as an output. There seems to have been attempts to fix it but the bug is still present in version 2.0.1. My solution to this was to detect if images are rotated and generate full size images that have the correct orientation which elventy-img is able to handle properly.

async function galleryImageShortcode(src, alt) {
...
const metadata = await sharp(src).metadata();
if (metadata.orientation > 1) {
console.log('Rotated image detected:', src, metadata.orientation);
await sharp(src).rotate().toFile(`correct/${src.split("/").pop()}`);
}
...
}

You might have also noticed the string replacement .replace(/(\r\n|\n|\r)/gm, "") This is to remove all the new lines in the generated HTML and prevent the markdown parser from generating empty paragraph tags.

All of this would generate the following HTML

<div class="gallery" id="gallery-uzupis">
<a href="/gen/1-tibeto-skveras-2000w.jpeg" data-pswp-height="1333" data-pswp-width="2000" target="_blank">
<img alt="Tiberto Skveras" src="/gen/1-tibeto-skveras-192w.jpeg">
</a>
<a href="/gen/2-tibetan-flags-2000w.jpeg" data-pswp-height="1333" data-pswp-width="2000" target="_blank">
<img alt="Tibetan Flags" src="/gen/2-tibetan-flags-192w.jpeg">
</a>
</div>
<script type="module">
import PhotoSwipeLightbox from '/js/photoswipe-lightbox.esm.min.js';
import PhotoSwipe from '/js/photoswipe.esm.min.js';
const lightbox = new PhotoSwipeLightbox({
gallery: '#gallery-uzupis',
children: 'a',
pswpModule: PhotoSwipe,
preload: [1, 1]
});
lightbox.init();
</script>

which would be styled with this CSS

.gallery {
margin: 32px 0;
column-count: 4;
column-gap: 8px;
}

.gallery a {
display: grid;
grid-template-rows: 1fr auto;
margin-bottom: 8px;
break-inside: avoid;
text-decoration: none;
}

.gallery img {
max-width: 100%;
display: block;
grid-row: 1 / -1;
grid-column: 1;
}

and voila!

Gallery built with eleventy-img
bash.lk logo
What do you think? Click start or continue discussion to leave a comment as a reply on the forum.