tl;dr? skip to the proof-of-concept/demo of lazy-loading CSS where possible while still loading it “conventionally” to users without Javascript
In a “daily tip” a couple of days ago, the excellent Chris Ferdinandi recommended an approach to loading CSS asynchronously based on a refined technique by Scott Jehl. The short of it is that you load your stylesheets like this:
<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">
You see what that’s doing? It’s loading the stylesheet for the print medium, but then when the document finishes loading it’s switching the media type from “print” to “all”. Because it didn’t apply to begin with the stylesheet isn’t render-blocking. You can use this to delay secondary styles so the page essentials can load at full speed.
I don’t like this approach. I mean: I love the elegance… I just don’t like the implications.
Why I don’t like lazy-loading CSS using Javascript
Using Javascript to load CSS, in order to prevent that CSS blocking rendering, feels to me like it conceptually breaks the Web. It certainly violates the expectations of progressive enhancement, because it introduces a level of fault-intolerance that I consider (mostly) unacceptable.
CSS and Javascript are independent of one another. A well-designed progressively-enhanced page should function with HTML only, HTML-and-CSS only, HTML-and-JS only, or all three.CSS adds style, and JS adds behvaiour to a page; and when you insist that the user agent uses Javascript in order to load stylistic elements, you violate the separation of these technologies (I’m looking at you, the majority of heavyweight front-end frameworks!).
If you’re thinking that the only people affected are nerds like me who browse with Javascript wholly or partially disabled, you’re wrong: gov.uk research shows that around 1% of your visitors have Javascript fail for some reason or another: because it’s disabled (whether for preference, privacy, compatibility with accessibility technologies, or whaterver), blocked, firewalled, or they’re using a browser that you didn’t expect.
Can we lazy-load CSS in a way that doesn’t depend on Javascript? (spoiler: yes)
Chris’s daily tip got me thinking: could there exist a way to load CSS in a non-render-blocking way but which degraded gracefully in the event that Javascript was unavailable? I.e. if Javascript is working, lazy-load CSS, otherwise: load conventionally as a fallback. It turns out, there is!
In principle, it’s this:
- Link your stylesheet from within a
<noscript>
block, thereby only exposing it where Javascript is disabled. Give it a custom attribute to make it easy to find later, e.g.<noscript lazyload>
(if you’re a standards purist, you might prefer to use adata-
attribute). - Have your Javascript extract the contents of these
<noscript>
blocks and reinject them. In modern browsers, this is as simple as e.g.[...document.querySelectorAll('noscript[lazyload]')].forEach(ns=>ns.outerHTML=ns.innerHTML)
.
If you need support for Internet Explorer, you need a little more work, because Internet Explorer doesn’t expose<noscript>
blocks to the DOM in a helpful way. There are a variety of possible workarounds; I’ve implemented one but not put too much thought into it because I rarely have to
think about Internet Explorer these days.
In any case, I’ve implemented a proof of concept/demonstration if you’d like to see it in action: just take a look and view source (or read the page) for details. Or view the source alone via this gist.
Lazy-loading CSS using my approach provides most of the benefits of other approaches… but works properly in environments without Javascript too.
Update: Chris Ferdinandi’s refined this into an even cleaner approach that takes the best of both worlds.
@scottjehl Oh snap!!
Oh snap!!
Read more →
@ChrisFerdinandi @scottjehl I’ve thought about this, too. Couldn’t we use the media swap technique _and… https://t.co/Ua8KvVeCP0
I’ve thought about this, too. Couldn’t we use the media swap technique _and_ provide a regular stylesheet link within the noscript block?
Read more →
I’m personally ok treating the web font as a progressive enhancement. Site still works fine without it.
Read more →
Not sure if I understand you correctly. In my case the deferred stylesheet contains way more than only a webfont. It’s has all the styling that is *not* in my critical CSS, so without JS a site could look quite broken “below the fold”.
Read more →
Oohhh yep. Sorry, was thinking about a very specific edge case.
For you, I would add a no script link to the full stylesheet, probably in the footer.
Read more →
Alight thanks for clarifying – that’s what I’m already doing (in the head though) 👍️
Read more →
@scottjehl Follow-up article that might be interesting to you, Dan: https://t.co/z7WHBcq93t
Follow-up article that might be interesting to you, Dan: gomakethings.com/progressive-en…
Read more →