The SMEAR technique enables infinite generations of simulations, games, or other CSS experiments to happen without any JS and without revealing the sauce to the User.
SMEAR stands for Shrouded Markup Editing And Regeneration - a description of what happens to advance the application state to the next generation without JS. The smearing gesture itself has to be more deliberate and slower than a swipe, and is where the name SMEAR actually comes from. Forcing SMEAR into an acronym happened after the fact as a terrible joke.
The SMEAR hack is somewhat involved in the details, so to navigate that complexity, let's walk backwards from the checkers demo and reveal the work one layer at a time.
!But first! Go hands-on with the SMEAR if you'd like (Chrome and Safari - desktop only) by selecting a front-row checker, then select a direction to move it in, then SMEAR to confirm:
Shrouded? What is the SMEAR hack doing?
The big reveal is right here in our first layer:
SMEAR is a drag-drop.
Code (CSS in this case) is on the right, and the user drags it into a contenteditable element (Style tag display block, contenteditable plaintext-only in this case) on the left.
We make the CSS and the style tag invisible to the user and put a fake call-to-action pseudo element in place for the user to seemingly-but-not-really interact with.
The last key at this layer is that the element with our code (shrouded markup) has user-select: all;
so any interaction selects all of the code automatically and the drag action begins immediately.
Here's an unshrouded bare bones demo of what's happening:
user-select: almost-all; How to change the code each generation
In order for the patch to be regenerated from our current state, we have to include markup that can programmatically represent any possible patch.
If we could somehow allow users to select pseudo element content, it would be much easier to generate the next generation's state from CSS. As it is now though, we have to omit the elements that don't apply to the next generation from a superset.
There are a few ways to omit text from the user-select: all;
user-select: none;
is surprisingly not one of them because it still copies the seemingly unselected text. Probably a bug.
display: none;
works but if your patch state is derived from an animation, you cannot set display
to none
. (Spoiler: we need to change our generated code from an animation hack)
visibility: hidden;
works and can be set from an animation. Yay!
The state of our patched code will come down to math - like showing the win screen when the opposite player's total sum/count of pieces on the board is 0. So we need to cast an integer from a calc()
to visibility: hidden;
, which requires an animation.
@keyframes markupToggler { from { visibility: hidden; } }
and
animation: markupToggler 1ms linear both var(--condition, 0);
If --condition
is 0, animation applies the from
state, if --condition
is 1, the animation stops applying the from
state.
Here's what that looks like in action: note the content of our CSS patch regenerates every time we SMEAR and shows the opposite state.
This animation can be reused on any element, just set the --condition
variable to a calc (or min/max/clamp/etc) that becomes 0 or 1 and you're good to go.
Forcing new code to the bottom of the style element
There are a number of ways to prevent the user from dragging code into the middle of existing code (something which you may have done while interacting with the previous demo?) but the most reliable I've used (which could be trimmed down or done differently) is just adding this:
style.shrouded-markup::before {
content: "";
display: block;
margin-bottom: -999999vh;
}
style.shrouded-markup::after {
content: "\A\A\A\A";
display: block;
}
Shrouding Reality
The only important gotcha here is that you'll want your fake call-to-action element to be on the ::before pseudo specifically, not ::after.
What's next?
Maybe a follow up article if there's interest!
- How to
user-select: all;
on contenteditable elements (you can!) and - using a textarea and reset button (JS not required) to empty cache.
The End!
If you think this idea/trick is neat, it's the kind of thing I do all the time! So please do consider following me here and on twitter as well!
💜
// Jane Ori
Top comments (1)
Yous should definitely send your articles to CSS Tricks 😉