Chris’ Corner: Esoteric Stuff in CSS
Listen I ain’t trying to scare you, but this CSS stuff can get complicated. It doesn’t have to be. CSS is just selectors with key value pairs in the end. The vast majority of CSS I write is pretty darn straightforward, especially once you have a general system (what files go where? how do we generally name things? how do we do variables?). But fair is fair, CSS can get wildly complex. It doesn’t help the complexity situation that anything new added increases that complexity, because, well, everything in CSS affects everything else. Selectors can get confusing and nesting can exacerbate that. Variables can get changed at any time and it’s not always clear from where. The situation on the page (sizing, events, settings) can effect what’s happening in CSS in increasingly bigger ways (querying containers, querying user preferences). If you want to push the edges of what CSS can do, now is the time.
I often save links of other people’s explorations of these edges of CSS. They are fascinating to me, naturally, as one of the proprietors of a front-end coding tool and ex-owner of CSS-Tricks. But sometimes I struggle to find a way to share them and contextualize them because of the complexity. In fact I think it’s fair to not use techniques based on complexity alone, after all, you write it you maintain it.
But let’s remove the limiter and look at CSS stuff regardless of how complex it might be.
I’ll start with one that has broken my brain for a few years now. @James0x57 calls this technique “Space Toggles” (who credits Ana Tudor for the discovery). Lea Verou calls it The --var: ;
hack. Just to lock in my own understanding, I called it The CSS Custom Property Toggle Trick.
The idea is that CSS custom properties can be valid and invalid, a valid value even just being " "
(a single space). You can concatenate that custom property with others to produce either-valid-or-invalid other custom properties. If it’s valid, you can do one thing, if it’s invalid, you can use the fallback for the custom property to do another thing. So it’s essentially if/then logic for custom properties.
The closest I ever came to simplifying it is this. And I admit it’s not exactly straightforward.
The good news is that CSS just decided that if()
would be a thing, so now we wait for browser implementations. It should be quite a bit more straightforward, although the ease-of-use will lead to more exotic usage of it and, I’m afraid to admit, more complex CSS.
Have you ever prepped for needing to do a sorting algorithm in a job interview? Can you explain what a Bubble Sort is? It’s the one where you loop through a list comparing the two things next to each other, and swap them if they need to be swapped. You keep running through the list until you don’t do any more swaps. I think, anyway, I’m not some computer genius over here. GrahamTheDev is though! Their article Bubble Sort…in PURE CSS? is bonkers.
This is the complexity that comes through the ability to compare variables (e.g. we have min()
and max()
in CSS now) and set new variables based on other variables. Write enough of that with delays and animations and you got yourself a bubble sort. Good luck grokking all that though.
And speaking of the edges of CSS:
warning: on mobile the last few animations might not play and just go blank. On PC your fan may spin up!
This is a limitation of using so many calculations that rely on previous calculations…I am not sure if it runs out of memory or what, but I defo pushed the limits of CSS here!
What’s the value of cos(25deg)
in CSS? Tyler Gaw wanted to figure it out exactly.
I know that will return a number between -1 and 1. But what number?
You can’t even set any property to exactly that, because just the number it produces is invalid for most properties.
You can do something like
width: calc(1px * cos(25deg))
then check thewidth
value in the devtools computed styles panel and get close, but not exact. Also,width: cos(25deg)
is invalid CSS and using a custom prop like--v: cos(25deg)
doesn’t really work either because the custom prop value is stored ascos(25deg)
.
But obviously cos(25deg)
produces a number?! Tyler found a way to extract the CSS-generated number in JavaScript. I felt like having a poke at it too, but I didn’t get much further. I did discover that the output is a unitless number, so it’s valid for CSS properties that take unitless numbers, like line-height
. So you can set line-height directly with it and then read the computed value. The problem is the computed value isn’t the direct output of the cos() function it’s the final px
value so… blech, hard.
There is some CSS trickery that involves @keyframe
animations. For example, styles applied during an animation apply extra strongly, so a style applied with a animation that runs for, say, a year is a way to do style overrides. Not recommended, heh. Setting animations to a particular place and pausing them is another weird trick for managing state in CSS.
Those, at least, I feel like I can get my brain around quickly. Animations have some extra power (and complexity) lately via Scroll-Driven Animations. Roman Komarov is the master recently of extremely exotic and complex CSS trickery, and his Scroll-Driven State Transfer is the pièce de résistance.
… an ability to mirror a particular state of some element — for example, hovered or focused — to an element in a different place on the page without a common or unique ancestral element that could have been used to deliver that state
You might think of :has()
as the new hotness for being able to access state anywhere in CSS from anywhere else, and you’d be right, but apparently this trick is a bit more adaptable, not requiring us to write more CSS later:
… we could implement this with the
:has()
selector, but for any new values we’d have to modify the stylesheet afterwards
It looks like the trick involves those Space Toggles too, meaning I think this is one of the most complicated CSS tricks I’ve ever seen. Apart from the super math-y stuff that always blows my mind, that is.
We’ve established that animations can be one of the vehicles for CSS complexity, which is true when using them just to animate, not strong-arm them into some wild state machine.
One way animations can get complicated is combining them. It was just the other day I reminded myself that nesting elements and then animating them both essentially doubles the speed of the inside animation. That’s just shooting a cannon from an airplane though, no fancy tech there. CSS actually allows us to manually combine animations, with tools like animation-composition
. It can be confusing though! Bramus wrote up The mysterious case of using CSS animation-composition: accumulate
on a scale
transform outlining just how weird it can get even in relatively simple situations. Maybe you’ll scratch your head too:
Accumulating a
scale(0.5)
withscale(2)
does not givescale(2.5)
butscale(1.5)
After scratching my own head about this long enough, it did sort of start to make sense. Even though blur(2)
and blur(3)
certainly make blur(5)
when accumulated, those are both “blur something more”. In the case of scale, 0.5 makes something smaller. So adding 2.0 + 0.5 isn’t 2.5 because they aren’t both “do something more”, one is “make smaller” and one is “make bigger”, hence the spec having to step in and explain.