Multiple Simultaneous Immutable Responsive Images
Two major improvements last night. Switching to versioned URIs was trivial thanks to the magical gulp-rev-all. I decided I wanted to apply it to images too, so I assumed I would have to jump through hoops. Turns out, this is some of the only software I’ve ever encountered that Just Works™ right out of the box. All hail the rev-all.
The other improvement was setting up responsive images. This was… harder. Every time I start a new project, it’s with a new permutation of tools and versions, and it eventually requires inventing a new way to deal with responsive images, but I couldn’t very well leave 6 MB of unoptimized full-size images in my entries.
Squish, squish
I started by adding gulp-imagemin to my images
task so that every image was being properly optimized. This actually caused a problem which I didn’t
notice until later: a few PNG images were turning transparent in the whites. It was pretty
odd to see a screenshot of my DotA profile where my name turned yellow when you hovered over
it (because it was inside a link and the background showed through the transparent sections).
Re-saving the original images with transparency explicitly turned off seemed to fix it. The only
remaining issue is that some ‘optimized’ images are actually larger than the originals!
I also tried using gulp-image, which provides useful
status information, but I wasn’t able to get it to work correctly. There was some sort of error
relating to svgo
.
Generation
Next, I looked up gulp-responsive-images,
which is my usual choice for generating the images themselves. I noticed a new package,
@tigersway/gulp-responsive, which was a
Complete rewrite of
gulp-responsive & gulp-responsive-config, with dependencies updated and in only one package
.
I pulled the shiny new thing into my configuration.
I always use img srcset
to mark up my images. I
created a JSON file with a list of widths I wanted to generate images at: 400, 800, 1200, and
1600 pixels.[1] Then I adjusted my Gulpfile to read the widths and started writing the
configuration for the responsive images plugin. That was when I realized my plan to create four
widths, a full-size image, and a thumbnail made no sense. I used this formula in the past because I
needed to create galleries, but in this case, all I wanted were small thumbnails that would link to
the full-size images. I pared down the configuration: two widths, with
Sharp settings to scale them down and crop them to squares.
It took some experimenting to figure out the correct syntax. This is what I arrived at:
JavaScriptfunction generateResponsiveImageConfig(imageConfig) {
const sizeConfigurations = [];
for (const size of imageConfig.widths) {
sizeConfigurations.push({
resize: {
width: size,
height: size,
fit: "cover",
position: "left top",
withoutEnlargement: true,
},
rename: { suffix: `.${size}w` },
});
}
return Object.freeze({
"**/*.png": sizeConfigurations,
"**/*.jpg": sizeConfigurations,
});
}
Markup
Now I needed to point to the right images in my HTML. These entries are written in Markdown,
where the syntax for images is 
. I could have used a Markdown plugin to
modify the output,[2] but I’m more comfortable with processing HTML, so I first looked up
11ty’s Transforms feature, then searched for a way
to transform the HTML. I wanted something which would give me both an easy method to search
for what I wanted to change and an easy method to replace it.
After some consideration, I chose PostHTML. Its model
looked simple and usable. I did find that you couldn’t match using a callback, making certain tasks
difficult. Fortunately, searching for <p><image-link … /></p>
was easy once I understood. Building
up the actual nodes was trivial too. I wired it up and it worked fine.
There was one more detail I wanted to address. Given an img
element with an srcset
, the browser
will decide which file to use based on the width of the window, but the width of the actual image
will likely be much smaller. HTML 5 provides the sizes
attributes to control
this. It has a specific syntax with restrictions on which units can be used inside it. I wanted to
devise a common system for both my Sass and my build to use so that the HTML sizes
attribute would always be in sync with my CSS.
I added a sizes
setting to my JSON file from earlier and looked around for a way to import
it in Sass. All I found were packages doing the opposite (converting Sass to JSON), so I gave
in and reversed it, adding sass-parser to my
dependencies. Sadly, this simply ignored complicated declarations like $larger-sizes: ("(min-width: 32em)": 75vw, "(min-width: 64em)": 28em)
. Unable to find a functional alternative, I started
perusing the documentation for Dart Sass itself and even considered parsing my Sass variables file
into an AST with sast, but I regained my perspective in
time and punted on the problem, just hard-coding the sizes for now. (Naturally, while I was looking
for Sass-to-JSON converters, I found a bunch of JSON-to-Sass converters, so I’ll
probably reverse it again and try one of those.) Problems aside, I’m happy to report I can now write
this in my entries:
HTML<image-link key="emacs-rust-syntax-highlighting.png" alt="An Emacs buffer showing a properly highlighted Rust file." />
And have it automatically turned into this:
HTML<a class="image-link" href="/assets/images/emacs-rust-syntax-highlighting.png"><img class="image-image" alt="An Emacs buffer showing a properly highlighted Rust file." src="/assets/images/emacs-rust-syntax-highlighting.400w.png" srcset="/assets/images/emacs-rust-syntax-highlighting.400w.png 400w, /assets/images/emacs-rust-syntax-highlighting.800w.png 800w" loading="lazy" sizes="(min-width: 32em) 75vw, (min-width: 64em) 28em, 50vw"></a>
Which is really all anyone can ask for in life.
Sharp on Netlify
I ran into a common issue with Sharp when I tried to build the site on Netlify. The situation got pretty complicated between pnpm, Netlify, Sharp, and npm. I couldn’t get it to build with a clean cache. In desperation, I switched entirely from pnpm to npm for this project, and figured out the right incantations for it all to work. I’m unhappy about switching away, but at least this isn’t a multi-package workspace.
Miscellany
- No more print-style paragraph indentation. I’ve always been fond of the style, but it didn’t sit well in mixed writing.
- Entry headings are now permalinks.
- There’s now a 404 page.
- Subscript and superscript text no longer affects the alignment of lines, thanks to a great tip from Scott Vandehey.
- I have fancy animated link backgrounds
now. I started by
copying the
::before
/::after
technique more or less from memory, but since line breaks broke the effect, I experimented a bit and landed on linear gradients with changingbackground-size
s. I can’t get enough of the effect. (background-color
isn’t affected bybackground-size
and linear gradients can’t be animated.) - I combined the Prism CSS files into a single Sass file at last.
- Lists look nicer.
- Nothing is minified any more. I may use a lot of tools but I don’t want the output to be opaque.
- The big things left to tackle are comments, archives/tag pages, and navigation.
The title is a reference to a 12-year-old meme video.