Today I'm going to talk about the most surprising and amazing technological development I found while creating Symbology: CSS custom properties, or "variables".
Finally, you can define names for values like re-used colors, like so:
...and then use them in other places, like so:
This is something I've used many templating languages to do before now, and it is so useful to have it supported directly in standard CSS.
And it's not the surprising and amazing part.
The surprising and amazing part is how scope works on these variables. In one sense, it is exactly like every other inherited CSS property. They cascade and they they accumulate. Symbology uses this in a number of ways. The simplest might be in the "hint arrows" that help you learn the rules of the game.
The g.hint .arrow style specifies a generic transform that depends on variables that are defined elsewhere. When adding the hints to the SVG, an additional class is added - left, right, above, or below - that sets the variable used in the rotate transform, to make the arrow point to the correct neighboring tile.
But there's a surprise second amazing part as well. You can do basic math with these variables! For example, Symbology specifies the animation of the twirling/bouncing pieces on the launch screen like this:
The base delay is five seconds, and then each piece from left to right adds an additional 100 milliseconds to that, via a --launch-pos-anim-delay variable. Sure, it would have been only a tiny irritation to specify the animation-delay as 5.0s, 5.1s, 5.2s, etc. in each position. But the way this method explicitly specifies the timing as base-plus-delay feels really feels like a clarity win.
Since these are just properties in regular CSS rules, they can be applied according to any matching pattern, such as the data attributes I discussed last time. Symbology is played on a grid. When I heard about CSS grids, I was excited. But from what I've tried, they don't work with SVG. This likely has to do with the fact that there is no sized "container" element in SVG, so there is nothing to fit the grid into. No matter, we can implement our own grid! With custom properties! And data attributes!
Above is the bit of SVG representing the game board, as it arrives from the server (with a little editing to make things clearer in this example). When we set up the game, we'll duplicate the g.playTile element and its child for each tile on the board.
This is the first bit of CSS we'll apply to it. It starts with some constants that will bound the game board. I'm trying to keep everything onscreen inside of a 450x800 "pixel" space (I'll discuss units in a minute). Subtracting the space for the header (where the next piece, trash, score, and such goes) as well as a little bit of padding, leaves me with 430x580 pixels for the game board. The third constant specifies that there should be 12 pixels between a tile and its neighbor.
After the constants, the size of a tile is computed. What I want is square tiles that fill the allotted space as much as possible. So first I compute the maximum size a tile could have in each dimension, without considering the other dimension, to get --playTile-width and --playTile-height. Then I find the smaller of the two, and store that in --playTile-size. Finally, --playTile-spacing is the distance between the centers of any two neighboring tiles. Finally, all of that is applied to the size of the tile background, and to the position of the tile.
Yes, I used data attributes in one case and classes in the other. I'll convert the classes to data attributes in the future, but I think it's interesting that there's so little difference in the CSS code that uses them:
Yes, we have arrived at the ugly and inflexible part. CSS in major browsers can't yet use data attribute values in custom property values directly. And that's why the choice of data attributes or classes doesn't matter here - we have to do the conversion manually in either case. Some time in the future we'll get to replace var(--playGrid-columns) with something like attr(data-width integer), and then it will make sense to also convert from .column0 to data-column="0". Then I can remove all 22 of these rules, while also gaining the ability to make grids even larger or smaller!
Before closing, I said I'd say something about units. I actually have two things to say. The first is that there is a tiny disconnect between CSS and SVG. SVG's measurement units are unlabeled. Everything is unitless points on a plane, that are only mapped to pixels by the viewBox attribute. But, CSS coordinates and sizes must have units, and so when setting properties via CSS, SVG measurements are labeled "pixels" via px regardless.
The second thing I have to say about units is, if you have ever worked through an object-oriented-programming tutorial's example of modeling measurement units via the type system, I think you'll giggle when doing CSS calculations. The reason for the multiplication by 1px in the CSS in this post is that, in addition to the size and position, I also need a scaling factor:
My game piece definitions are based on a 100x100 unit grid. I need to scale them down to fit the tiles. Scaling factors in CSS must be unitless, but the tile size (width and height in the earlier definition) must be in px units. It's impossible to remove units from a CSS value once applied (division requires the divisor to be unitless, so / 100px is invalid), so instead I do all the math without units, then apply pixels to the appropriate values via * 1px when necessary.
Post Copyright © 2022 Bryan Fink