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:
function 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:
<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:
<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/::aftertechnique more or less from memory, but since line breaks broke the effect, I experimented a bit and landed on linear gradients with changingbackground-sizes. I can’t get enough of the effect. (background-colorisn’t affected bybackground-sizeand 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.