I’ve started trialling different HTML and technologies for the “simple” responsive images (i.e. not art-directed per breakpoint) used in blog articles on this site. I’m continuing to lean on Cloudinary as my free image host, CDN and format-conversion service. But at the HTML level I’ve moved from a complicated
<img srcset> based approach which included many resized versions of the same image. I now use a simpler
<source> based pattern that keeps the number of images and breakpoints low, leaning instead into the performance gains offered by the newer image formats
Recently I’ve noticed some developers recommending using the CSS
aspect-ratio property directly on images. My understanding of
aspect-ratio was that it’s not so much intended for elements like
img which already have an intrinsic aspect ratio, but rather for the likes of
div which do not. Furthermore, when the goal is to prevent the layout shift that can occur after an image loads we should supply our images with
height HTML attributes rather than using CSS.
In this timely post, Jake helpfully explains how
height attributes are used by CSS as presentation hints to automatically set an
aspect-ratio that will also, in cases where the attributes were set wrongly, fall back to the image’s intrinsic aspect ratio. Therefore, concentrating on HTML alone is ideal for our content images. My previous approach seems sound but I now know a little more about why.
Jeremy Keith’s new course for Google’s web.dev learning platform is fantastic and covers a variety of aspects of responsive design including layout (macro and micro), images, icons and typography.
Harry Roberts (who created ITCSS for organising CSS at scale but these days focuses on performance) has just given a presentation about the importance of getting the content, order and optimisation of the
<head> element right, including lots of measurement data to back up his claims. Check out the slides: Get your Head Straight
I’ve been really interested in the subject of Web Performance since I read Steve Souders’ book High Performance Websites back in 2007. Although some of the principles in that book are still relevant, it’s also fair to say that a lot has changed since then so I decided to pull together some current tips. Disclaimer: This is a living document which I’ll expand over time. Also: I’m a performance enthusiast but not an expert. If I have anything wrong, please let me know.
Astro looks very interesting. It’s in part a static site builder (a bit like Eleventy) but it also comes with a modern (revolutionary?) developer experience which lets you author components as web components or in a JS framework of your choice but then renders those to static HTML for optimal performance. Oh, and as far as I can tell theres no build pipeline!
Astro lets you use any framework you want (or none at all). And if most sites only have islands of interactivity, shouldn’t our tools optimize for that?
I love hearing about clever front-end solutions which combine technologies and achieve multiple goals. In Zach’s post we hear how Netlify’s website suffered from layout shift when conditionally rendering dismissible promo banners, and how he addressed this by rethinking the problem and shifting responsibilities around the stack.
Here’s my summary of the smart ideas covered in the post:
- decide on the appropriate server-rendered content… in this case showing rather than hiding the banner, making the most common use case faster to load
- have the banner “dismiss” button’s event handling script store the banner’s
hrefin the user’s localStorage as an identifier accessible on return visits
<head>… in this case a check for this banner’s identifier existing in localStorage
- under certain conditions – in this case when the banner was previously seen and dismissed – set a “state” class (
banner--hide) on the
<html>element, leading to the component being hidden seamlessly by CSS
- build the banner as a web component, the first layer of which being a custom element
- delegate responsibility for presenting the banner’s “dismiss” button to the same script responsible for the component’s enhancements, meaning that a broken button won’t be presented if that script were to break.
So much to like in there!
Here are some further thoughts the article provoked.
Web components FTW
It feels like creating a component such as this one as a web component leads to a real convergence of benefits:
- tool-free, async loading of the component JS as an ES module
- fast, native element discovery (no need for a
- enforces using a nice, idiomatic class providing encapsulation and high-performing native callbacks
- resilience and progressive enhancement by putting all your JS-dependent stuff into the JS class and having that enhance your basic custom element. If that JS breaks, you still have the basic element and won’t present any broken elements.
Even better, you end up with framework-independent, standards-based component that you could share with others for reuse elsewhere, just like Zach did.
I could see there being a case where there are multiple banners during the same time period. I guess in that situation the localStorage
banner value could be a stringified object rather than a simple, single-URL string.
Setting context on the root
It’s really handy to have a way to exert just-in-time control over the display of a server-rendered element in a way that avoids flashes of content… and adding a class to the
We’re already familiar with the technique of placing classes on the root element from libraries like modernizr and some font-loading approaches, but this article serves as a reminder that we can employ it whenever we need it.
Handling the close button
I think what Zach’s saying is that the alternatives – inserting the button with JS, or toggling the
hidden attribute or its CSS counterpart
display:none – would affect geometry causing the browser to perform layout… whereas modifying opacity does not.
If I were going down Zach’s route I think I’d replace
visibility since the latter hiding method removes the hidden element from the document which feels more accessible, while still avoiding triggering the reflow that
In a server-side scripted application – one using Rails or PHP, for example – you could alternatively handle persisting state with cookies rather than localStorage… allowing you to test for the presence of the cookie on the server then handle conditional rendering of the banner on the server too, rather than needing classes which trigger hiding. I can see an argument for that. Thing is though, not everyone’s working in that environment. Zach has provided a standalone solution.
I like the sound of this performance analysis tool from the clever folks at Cloud Four, especially because it covers your entire site rather than just a single page.
Lighthouse Parade is a Node.js command line tool that crawls a domain and gathers lighthouse performance data for every page. With a single command, the tool will crawl an entire site, run a Lighthouse report for each page, and then output a spreadsheet with the aggregated data.
Even though more and more people get access to the internet every day, not all of them have fast gigabit connections or unlimited data. Using the media query prefers-reduced-data we can keep our sites accessible to everyone.
Daniel Post shared a really cool performance-optimisation trick for Eleventy on Twitter the other day. When statically generating your site you can loop through your pages and, for each, use PurgeCSS to find the required CSS, then inline that into the
<head>. This way, each page contains only the CSS it needs and no more!
I’ve just installed this on my personal site. I was already inlining my CSS into the
<head> but the promise of only including the minimum CSS that each specific page needs was too good to resist.
Turned out it was a breeze to get working, a nice introduction to Eleventy transforms, and so far it’s working great!
I’ve been thinking about Scott Jehl’s “simplest way to load external CSS asynchronously” technique. I’m interested in its use of an inline (
Scott Jehl of Filament Group demonstrates a one-liner technique for loading external CSS files without them delaying page rendering.
A combination of asynchronously loading CSS, asynchronously loading font files, opting into FOFT, fast-fetching asynchronous CSS files, and warming up external domains makes for an experience several seconds faster than the baseline.
Here’s a brilliant discussion between Gerry McGovern and Jeremy Keith on that problem, suggesting tactics to help fix things such as performance budgets, introducing tactics at the design stage to mimic slow connections and other access constraints, optimising for return visits, progressive enhancement and more.
In cases of emergency, many organizations need a quick way to publish critical information. But existing (CMS) websites are often unable to handle sudden spikes in traffic.
Check whether or not a CSS property is a good candidate for smooth animation based on whether updates to its value trigger expensive changes (to, for example, “element geometry”) causing layout updates and repaints.
For many years I’ve placed script elements just before the closing
body tag rather than in the
<head>. Since a standard
<script> element is render-blocking, the theory is that by putting it at the end of the document – after the main content of the page has loaded – it’s no longer blocking anything, and there’s no need to wrap it in a
DOMContentLoaded event listener.
It turns out that my time-honoured default is OK, but there is a better approach.
This is a very clever idea via Arthur Corenzan. Rather than use the default YouTube embed, which adds a crapload of resources to a page whether the user plays the video or not, use the little tiny placeholder webpage that is just an image you can click that is linked to the YouTube embed.
When it comes to webfonts, if you want to serve an accessible and high performance experience across device types it’s not as straightforward as just specifying your fonts in CSS then hoping for the best.
Now that we have the HTML attribute
loading we can set
loading="lazy" on our website’s media, and the loading of non-critical, below-the-fold media will be deferred until the user scrolls to them.
See all tags.