I made a gallery. And I think I've finished tinkering with it.
It's static. There ain't no js
here.
motivation
To have somewhere to put my maps. And maybe, maybe, this will motivate me to make a few more. At the moment it is mostly populated with ones made during the last two 30 Day Map Challenges.
assistance
I leaned heavily on:
- the
eleventy-photo-gallery
, code for which can be found in this repo, by Tanner Dolby
Along the way to my solution, I tried and borrowed various bits and pieces from
- Valentin Pratz's slightly improved gallery
- Prabashwara Seneviratne's gallery tutorial
- Jeremy Robert Jones' flexbox magic
- Sylvia van Os' CSS only version of photoswipe: CSSBox
how
the data
A maps.json
file in src/_data
that looks a bit like:
[
{
"title": "the title of my map",
"src": "where/it/is/saved.png",
"alt": "appropriate alt text - this is a must",
"caption": "A nice caption",
"description": "this is for open graph",
"tools": "what i used to make the map (QGIS, python, pen & paper)",
"data": "data sources",
"font": "SuperCursive, LameSerifs"
}
]
Whilst editing text in a json
is a bit gross and annoying at times, this seemed like a reasonably rational choice.
the shortcode
All of my shortcodes live in _config/shortcodes.js
[1]. There the following was added:
/* for gallery images */
const PREVIEW_HEIGHT = 300;
async function img(image, css, loading='lazy') {
if (image.alt == undefined) {
throw new Error(`missing alt`);
}
let metadata = await Image(image.src, {
formats: ["webp"],
widths: [200, 300, 480, 640, 1024, 2048],
outputDir: './src/images',
urlPath: "/images/",
filenameFormat: function (id, src, width, format, options) {
const ext = path.extname(src);
const name = path.basename(src, ext);
return `${name}-${width}w.${format}`;
},
htmlOptions: {
imgAttributes: {
loading: "lazy",
decoding: "async"
},
pictureAttributes: {class: "something"}
},
});
let preview = metadata.webp.find(i => i.height >= PREVIEW_HEIGHT) || metadata.webp[0];
let full = metadata.webp[metadata.webp.length - 1];
const srcset = Object.values(metadata).map((imageFormat) => {
return imageFormat.map(entry => entry.srcset).join(", ");
})
if (css == 'preview') {
let slug = slugify(image.title);
return `
<a href="/mapping/gallery/${slug}">
<img
class="${css}"
src="${preview.url}"
alt="${image.alt}"
>
</a>
`.replace(/(\r\n|\n|\r)/gm, "")
} else {
return `
<img
src="${full.url}"
alt="${image.alt}"
srcset="${srcset}"
fetchpriority="high"
loading="eager"
sizes="auto"
/>
`
}
}
// export
export const shortcodes = (eleventyConfig) => {
eleventyConfig.addShortcode("img", img);
};
Is this perfect? No.
Could this be improved? Yes.
the loop
A new page[2] was created. And there the following bit of html
/ nunjucks
:
<div class="outerGallery">
{%- for map in maps -%}
<div class='gallery'>
{%- img map, 'imgPreview' -%}
</div>
{%- endfor -%}
</div>
</div>
This loops through all the items in maps.json
, and applies the img
shortcode to each one. Bingo.
the syling
A bit more css
than usual. This is inlined with {% include "./gallery/galleryStyles.njk" %}
[3].
The whole page has some hackneyed contours in the background[4]. The background .svg
changes between dark and light mode like so
[data-theme='dark'] body {
background-image: url('/assets/contoursDark.svg');
}
[data-theme='light'] body {
background-image: url('/assets/contours.svg');
}
The outerGallery
spans 90vw
, and has a fixed height of 340px
, and it can be scrolled horizontally. Each inner gallery
item, and the styling on the img
itself, means that all images are scaled to same height. On hover there's a transform: scale(1.05) rotate(3deg)
and the box-shadow
gets a bit bigger.
The images here are the preview ones generated by the img
shortcode - so they're nice and small. And, with the loading=lazy
attribute, the images only load when scrolled to. So the whole thing is pretty snappy[5].
Clicking on a map from the gallery takes you to its individual page.
the pages
Three cheers for 11ty pagination
In src/mapping/gallery.njk
is the front matter:
---
layout: "layouts/mapGallery.njk"
pagination:
data: maps
size: 1
alias: map
permalink: "/mapping/gallery/{{ map.title | slugify }}/"
---
which makes a page for each map in the maps.json
, and gives it a sensible url
.
layouts/mapGallery.njk
isn't too different from my base
layout, but here I make use of the metagen plugin to populate some page metadata:
{% metagen
title=map.title,
desc=map.description,
url=url + map.title | slug + "/",
img=map.src,
alt=map.alt
%}
A higher resolution version of the map is shown, and it's styled to try and take up 95svh
, provided that wouldn't make it wider than the viewport. fetchpriority="high"
and loading="eager"
means[6], the image gets loaded asap.
The image comes with alt text, and a nice caption. A table below shows the tools used to make the map, along with any data sources and fonts used.
Finally, at the bottom there's the navigation, which grabs the name of the next/previous map from the pagination
object...
<nav style='display:flex; flex-flow: column nowrap; justify-content:center; align-items: center; margin:2rem auto;'>
{% if pagination.href.previous %}
<a class="link" href="{{ pagination.href.previous }}">← {{ pagination.page.previous.title }} ←</a>
{% endif %}
<span style="margin: 0.5lh 0;">back to the <a class="link" href="/mapping">map room</a></span>
{% if pagination.href.next %}
{% if not pagination.href.previous %}
<a class="link" href="{{ pagination.href.next }}">→ {{ pagination.page.next.title }} →</a>
{% else %}
<a class="link" href="{{ pagination.href.next }}">→ {{ pagination.page.next.title }} →</a>
{% endif %}
{% endif %}
</nav>
still to do
make some more maps.
happy browsing
footnotes
alongside
_config/index.js
which has the lineexport { shortcodes } from "./shortcodes.js"
, and they're added to.eleventy.js
witheleventyConfig.addPlugin(shortcodes)
↩︎src/mapping/index.md
↩︎feel free to right-click insepct on the mapping page - if you must ↩︎
contours of my favourite place, courtesy of Copernicus Global DEM (ESA) ↩︎
full marks on lighthouse, thank you very much ↩︎
i think ↩︎