New site part 4 - images
Posted on 20 October, 2023
Part of a series about setting up the new 11ty version of this site
One of the things that I like about WordPress is how it deals with images for you — so I want to leverage eleventy-img to make using images as simple and repeatable as possible.
Starting generically
I'm going to start out by writing an 11ty shortcode to produce an image based on arbitrary inputs. As I've previously set out I'll keep the detailed code in a separate JS file and import the function into my eleventy config.
// going to need Image from eleventy-img, and path
const Image = require("@11ty/eleventy-img");
const path = require("path");
const generateImageTags = async (data) => {
// allow for arbitrary:
// - src (file-path)
// - alt text
// - image widths, provided as an array in pixels
// - div_width, the 'ideal' width of the image in points
// - loading, text, either eager or lazy, defaults to lazy
// - caption, text to be displayed under the image
const { src, alt, widths, div_width, sizes, loading, caption } = JSON.parse(data);
// set the urlPath based on the actual image path
const urlPath = path.dirname(src).replace("./src/images", "/img") + "/";
// generate the images and get metadata for them; generating webp and jpeg fallback,
// with custom filename based on the original filename plus the width
const metadata = await Image(src, {
widths: widths,
formats: ["webp", "jpeg"],
outputDir: `./_site${urlPath}`,
urlPath: urlPath,
filenameFormat: function (id, src, width, format, options) {
const extension = path.extname(src);
const name = path.basename(src, extension);
return `${name}-${width}w.${format}`;
}
});
// attributes from arbitrary inputs; the image will be included within a separate
// containing div of defined width (and max-width) so the style attribute will let
// the image be laid out properly before it's finished loading
const attributes = {
alt: alt,
loading: loading || 'lazy',
decoding: "async",
sizes: sizes,
style: "width: 100%; height: auto;"
}
// generate the HTML for the image
const imageHTML = Image.generateHTML(metadata, attributes);
// generate a containing div for the image HTML generated, plus a styled
// caption if provided
let output = `
<div style="width: ${div_width}; max-width: 100%; margin: 0 auto;">
${imageHTML}
</div>`
if (caption && caption != '') {
output = output + `
<p class="caption" style="width: ${div_width}; max-width: 100%;">
<i>${caption}</i>
</p>`
}
return output;
}
Concrete uses
Having made a generic (abstract) shortcode, I now want some specific codes based on typical image widths. My main content div is going to be a maximum 900 pixels wide, and I know I'll have some images that should be narrower on a large screen, so have written concrete versions for 300, 600 and 900-wide images.
Example for the 900-wide case (termed 'full-width'):
// function to generate HTML for a full-screen width image, with alternate
// smaller sizes
const generateFullWidthImage = async (data) => {
// only need the src, alt text, loading status and caption
const { src, alt, loading, caption } = JSON.parse(data);
// generate the data required by the generic version; note:
// - selection of widths and allow for 2x versions
// - sizes generated using RespImageLint,
// https://ausi.github.io/respimagelint/
const newData = {
src: src,
alt: alt,
loading: loading || 'lazy',
caption: caption,
widths: [300, 400, 600, 800, 900, 1200, 1800],
div_width: "900px",
sizes: "(min-width: 1300px) 900px, (min-width: 980px) calc(63.33vw + 89px), 93.94vw"
}
const output = await generateImageTags(JSON.stringify(newData));
return output;
}
Using in markdown
To use this in markdown, I'm actually writing Nunjucks in my markdown file. First we set up the data with the image information in, and then we send that to a shortcode. This works out-of-the-box (at least with 11ty v2), no need to add templating config.
{% assign imageData = '{
"src": "./src/images/2016/10/xkcd-tech-support-cheat-sheet.png",
"alt": "Tech Support (XKCD)",
"caption": "Tech support cheat sheet",
"loading": "eager"
}'
%}
{% generate600WideImage imageData %}