Journal
Observer APIs in a nutshell
I’ve played with the various HTML5 Observer APIs (IntersectionObserver, ResizeObserver and MutationObserver) a little over the last few years—for example using ResizeObserver in a container query solution for responsive grids. But in all honesty their roles, abilities and differences haven’t yet fully stuck in my brain. So I’ve put together a brief explainer for future reference.
Intersection Observer
Lets you watch for when an element of your choice intersects with a root element of your choice—typically the viewport—and then take action in response.
So you might watch for a div that’s way down the page entering the viewport as a result of the user scrolling, then act upon that by applying a class which animates that div’s opacity from 0 to 1 to make it fade in.
Here’s how it works:
- Instantiate a new
IntersectionObserverobject, passing in firstly a callback function and secondly an options array which specifies your root element (usually the viewport, or a specific subsection of it). - Call
observeon your instance, passing in the element you want to watch. If you have multiple elements to watch, you could callobserverepeatedly in a loop through the relevantNodeList. - in the callback function add the stuff you want to happen in response to “intersecting” and “no longer intersecting” events.
Mutation Observer
Lets you watch for changes to the attributes or content of DOM elements then take action in response.
You might use this if you have code that you want to run if and when an element changes because of another script.
Here’s how it works:
- Your typical starting point is that you already have one or more event listeners which modify the DOM in response to an event.
- Instantiate a new
MutationObserverobject, passing in a callback function. - The callback function will be called every time the DOM is changed.
- Call
observeon your instance, passing in as first argument the element to watch and as second argument a config object specifying what type of changes you’re interested in, for example you might only care about changes to specific attributes. - your callback function provides an array of MutationRecord objects—one for each change that has just taken place—which you can loop through and act upon.
Resize Observer
Lets you watch for an element meeting a given size threshold then take action in response.
For example you might add a class of wide to a given container only when it is wider than 60em so that new styles are applied. This is a way of providing container query capability while we wait for that to land in CSS.
Or you might load additional, heavier-weight media in response to a certain width threshold because you feel you can assume a device type that indicates the user is on wifi. Adding functionality rather than applying styles is something we could not achieve with CSS alone.
Given that Container Query support is coming in CSS and that we can usually get by without it in the meantime, I don’t think it’s something I need so desperately that I’ll keep reaching for JavaScript to achieve it. However that’s not the only use for ResizeObserver.
It’s also worth remembering that it’s not all about width: ResizeObserver can also be used to detect and respond to changes to an element’s height. An example might be watching for changes to a chat window’s height—something that’s liable to happen as new messages appear—and then ensuring the focus stays at the bottom, on the latest message.
References
Steve Griffiths has some great video tutorials on these topics.
Design-ish systems (by Ethan Marcotte)
Here’s an interesting new article from Ethan Marcotte, in which he muses on better ways to think about Design Systems based on his recent experience.
Once you’ve identified the root causes, you’ll be in a far, far better place to choose the right things — and, more importantly, to create a system that finally supports your design.
Here’s what I took from it:
We can consider the difference between things, issues and broader goals.
It’s easy to focus on the things (design tokens, choice of design tool, programming language).
But things are just elements of the system; they don’t solve design systems.
Focusing too much or too early on the things leads to a tendency to patch problems.
Whereas discussing the current wider organisational issues we need to address leads to defining broader goals.
This in turn helps us choose and focus on the right things for the right reasons.
Diffchecker - Online diff tool
Diffchecker is a diff tool to compare text differences between two text files.
I had cause to use this free diff tool recently to compare two large minified CSS files and it did the trick better than any others I’ve tried. Thumbs up!
Building a resilient frontend using progressive enhancement (on GOV.UK)
GOV.UK’s guidance on developing using progressive enhancement is pretty great in all departments. It begins with this solid advice:
you should start by making your page work with just HTML, before adding anything else like Cascading Style Sheets (CSS) and JavaScript. This is because HTML is the most resilient layer. If the HTML fails there’s no web page. Should the CSS or JavaScript fail, the HTML will still render correctly.
I particularly like the section where they address the misconception that a resilient baseline is only required in places where the user has explicitly disabled JavaScript and therefore not worth worrying about.
You should not assume the reason for designing a service that works without CSS or JavaScript is because a user chooses to switch these off. There are many situations when extra layers can fail to load or are filtered.
As their subsequent list of scenarios illustrates, a user turning JavaScript off is probably the least likely of a range of reasons why extra layers on top of HTML can fail.
Relatedly, I’ve often found that Everyone has JavaScript, right? serves as a great go-to reference for these sorts of conversations around resilience.
I’ve started reading Recursion by Blake Crouch.
Encapsulated Eleventy/Nunjucks components with macros (by Trys Mudford)
Trys shows us how to use the Nunjucks macro to create encapsulated components. This works out less leaky and more predictable than an include preceded by variables assigned with set.
Trys’s solution allows us to render components like so:
{{ component('button', {
text: 'Press me'
}) }}
{# Output #}
<button type="button">Press me</button>
Update, 8th Aug 2021: when I tried implementing this I found that it results in errors when attempting to render components anywhere other than on the base template where Trys recommended including the import line. The workaround—as Paul Salaets points out—is to include the import at the top of every page-level template (index.njk, archive.njk etc) that uses the component macro.
In Praise of the Unambiguous Click Menu (on CSS-Tricks)
Mark Root-Wiley explains why navigation menus that appear on click rather than hover are better.
I like the fact that it calls out that:
When you first make this change, it’s true that some visitors might still expect hover menus. They may even say they prefer them if you ask.
But then goes on to provide some rationale (ammunition?) from various big guns on why click menus are better.
From the US Web Design System:
Avoid using hover to expand dropdown lists. Hover is difficult for some users and won’t work on touch screens. Dropdowns should expand on click or with keyboard navigation.
From popular frontend framework Bootstrap:
What it really boils down to is user intent. The purpose of a hover state is to indicate something is clickable (underlined text). The purpose of a click is to actually do something, to take an explicit action. Opening a dropdown is an explicit action and should only happen on click.
(via @jamesmockett)
How to Favicon in 2021 (on CSS-Tricks)
Some excellent favicon tips from Chris Coyier, referencing Andrey Sitnik’s recent article of the same name.
I always appreciate someone looking into and re-evaluating the best practices of something that literally every website needs and has a complex set of requirements.
Chris is using:
<link rel="icon" href="/favicon.ico"><!-- 32x32 -->
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png"><!-- 180x180 -->
<link rel="manifest" href="/manifest.webmanifest">
And in manifest.webmanifest:
{
"icons": [
{ "src": "/192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/512.png", "type": "image/png", "sizes": "512x512" }
]
}
(via @mxbck)
How I subset web fonts
On my personal website I currently use three web fonts from the Source Sans 3 group: regular, italic and semibold. I self-host my fonts because that’s a good practice. Additionally I use a variety of special characters to add some typographic life to the text.
When self-hosting it’s important from a performance perspective to minimise the weight of the font files your visitors must download. To achieve this I subset my fonts so as to include only the characters my pages use but no more. Here’s how I do it.
Note: to follow these steps, you’ll need to install glyphhanger. The Github page includes installation and usage guidelines however there are a few common installation pitfalls so if you’re on a Mac and run into trouble I recommend checking Sara Soueidan’s How I set up Glyphhanger on macOS to get you back on track.
For the purposes of this walkthrough I’ll assume you have a directory in your application named fonts.
Start by deleting any existing custom font files from your application’s fonts directory.
Run your site locally in an incognito browser window. For my Eleventy-based site, I run npm run serve which serves the site at http://localhost:8080.
Visually check your locally-running site to ensure that now you’ve deleted your web fonts it’s no longer serving them and is instead rendering text using system fonts.
Visit the source page for your custom fonts—in my case the Github repository for Source Sans 3. Download in .ttf format the latest versions of the fonts you need and place them in your fonts directory. For me these are:
- Regular,
- Italic; and
- Semibold.
You’ll notice the large file sizes of these .ttf files. For example Source Sans 3’s Regular.ttf font is 299 kb.
At the command line, cd into your fonts directory.
Now we’re going to run glyphhanger on one font at a time. This fantastic tool will intelligenty crawl your website to check which glyphs are currently in use for the specified weight then include those in a subset file which it outputs in .ttf, .woff and .woff2 formats. I use glyphhanger’s spider option so that it spiders multiple pages (rather than just one) at a time, meaning that it is more likely to catch all the special characters I’m using.
glyphhanger http://localhost:8080/posts/ --subset=SourceSans3-Regular.ttf --spider-limit=0
If all went well you should see output like this:
U+20-23,U+25-2A,U+2C-5B,U+5D,U+5F,U+61-7D,U+A9,U+B7,U+BB,U+D7,U+E9,U+F6,U+200B,U+200E,U+2013,U+2014,U+2018,U+2019,U+201C,U+201D,U+2026,U+2122,U+2190,U+2192,U+2615,U+FE0F
Subsetting SourceSans3-Regular.ttf to SourceSans3-Regular-subset.ttf (was 292.24 KB, now 46.99 KB)
Subsetting SourceSans3-Regular.ttf to SourceSans3-Regular-subset.zopfli.woff (was 292.24 KB, now 22.14 KB)
Subsetting SourceSans3-Regular.ttf to SourceSans3-Regular-subset.woff2 (was 292.24 KB, now 17.77 KB)
The .woff2 subset file has reduced the file size from 299 kb to 17.77 kb which is pretty impressive!
Update your CSS to point at the new woff2 and woff subset files for your font. My updated CSS looks like this:
@font-face {
font-family: Source Sans Pro;
src: url(/fonts/sans/SourceSans3-Regular-subset.woff2) format("woff2"),
url(/fonts/sans/SourceSans3-Regular-subset.zopfli.woff) format("woff");
font-weight: 400;
font-display: swap;
}
Check your locally running application to ensure that the relevant text (body copy in this case) is now being served using the web font rather than fallback font, and that special characters are also being served using the web font.
I’ll usually crack open the Fonts panel in Firefox’s DevTools and check that, amongst other things, my pagination links which use the rightward pointing arrow character (→ or unicode U+2192) are rendering it using Source Sans Pro and not sticking out like a sore thumb by using Helvetica due to the glyph not being present in the subset.
Delete the .ttf file you started with and any .ttf subsets generated, because you won’t serve files in that format to your website visitors.
Repeat the glyphhanger subsetting and CSS updating process for any other weights (italic, semibold) or custom fonts you want to subset.
One last handy tip: if there’s a weight for which I don’t need a fancy character set (for example the Semibold I use for headings), I might just grab default latin charset woff and woff2 files from the Google Webfonts Helper. The files tend to be small and well-optimised and this can save a little time. (This is only possible if the font is available from Google Fonts which is true in the case of Source Sans 3.)
I’ve started reading The Rise of the Ultra Runners, by Adharanand Finn.