Logical Operations with CSS Variables

Fairly often, whereas utilizing change variables (a variable that is both zero or 1, an idea that is defined in a higher element in on this publish), I want I may carry out logical operations on them. We do not have capabilities like not(var(–i)) or and(var(–i), var(–k)) in CSS, however we will emulate these and extra with arithmetic operations in a calc() perform.

This text goes to point out you what calc() formulation we have to use for every logical operation and clarify how and why they’re used with a few use circumstances that result in the writing of this text.

How: the formulation

not

It is a fairly easy one: we subtract the change variable (let’s name it –j) from 1:

–notj: calc(1 – var(–j))

If –j is zero, then –notj is 1 (1 – zero). If j is 1, then –notj is zero (1 – 1).

and

Now, when you’ve ever taken electronics lessons (significantly one thing like Programmed Logic Techniques or Built-in Circuits), then you definately already know what formulation we have to use right here. However let’s not bounce straight into it.

The and of two operands is true if and provided that each are true. The 2 operands in our case are two change variables (let’s name them –k and –i). Every of them could be both zero or 1, independently of the opposite. This implies we could be in a single out of 4 doable eventualities:

–k: zero, –i: zero
–k: zero, –i: 1
–k: 1, –i: zero
–k: 1, –i: 1

The results of the and operation is 1 if each our change variables are 1 and zero in any other case. it the opposite approach, this result’s zero if at the very least one of many two change variables is zero.

Now you might want to consider it this manner: the results of what arithmetic operation is zero if at the very least one of many two operands is zero? That is multiplication, as multiplying something by zero provides us zero!

So, our –and formulation is:

–and: calc(var(–k)*var(–i))

Contemplating every of our 4 doable eventualities, we now have:

for –k: zero, –i: zero, we now have that –and is zero (zero*zero)
for –k: zero, –i: 1, we now have that –and is zero (zero*1)
for –k: 1, –i: zero, we now have that –and is zero (1*zero)
for –k: 1, –i: 1, we now have that –and is 1 (1*1)

nand

Since nand will not be and, we have to substitute the –j within the not formulation with the formulation for and:

–nand: calc(1 – var(–k)*var(–i))

For every of our 4 doable eventualities, we get:

for –k: zero, –i: zero, we now have that –nand is 1 (1 – zero*zero = 1 – zero)
for –k: zero, –i: 1, we now have that –nand is 1 (1 – zero*1 = 1 – zero)
for –k: 1, –i: zero, we now have that –nand is 1 (1 – 1*zero = 1 – zero)
for –k: 1, –i: 1, we now have that –nand is zero (1 – 1*1 = 1 – 1)

or

The results of the or operation is 1 if at the very least one among our change variables is 1 and zero in any other case (if each of them are zero).

The primary intuition right here is to go for addition, however whereas that provides us zero if each –k and –i are zero and 1 if one is zero and the opposite one is 1, it provides us 2 if each of them are 1. So that does not actually work.

However we will use the nice previous De Morgan’s legal guidelines, one among which states:

not (A or B) = (not A) and (not B)

This implies the results of the or operation is the negation of the and operation between the negations of –k and –i. Placing this into CSS, we now have:

–or: calc(1 – (1 – var(–k))*(1 – var(–i)))

For every situation, we get:

for –k: zero, –i: zero, we now have that –or is zero (1 – (1 – zero)*(1 – zero) = 1 – 1*1 = 1 – 1)
for –k: zero, –i: 1, we now have that –or is 1 (1 – (1 – zero)*(1 – 1) = 1 – 1*zero = 1 – zero)
for –k: 1, –i: zero, we now have that –or is 1 (1 – (1 – 1)*(1 – zero) = 1 – zero*1 = 1 – zero)
for –k: 1, –i: 1, we now have that –or is 1 (1 – (1 – 1)*(1 – 1) = 1 – zero*zero = 1 – zero)

nor

Since nor will not be or, we now have:

–nor: calc((1 – var(–k))*(1 – var(–i)))

For every of our 4 doable eventualities, we get:

for –k: zero, –i: zero, we now have that –nor is 1 ((1 – zero)*(1 – zero) = 1*1)
for –k: zero, –i: 1, we now have that –nor is zero ((1 – zero)*(1 – 1) = 1*zero)
for –k: 1, –i: zero, we now have that –nor is zero ((1 – 1)*(1 – zero) = zero*1)
for –k: 1, –i: 1, we now have that –nor is zero ((1 – 1)*(1 – 1) = zero*zero)

xor

The results of the xor operation is 1 when one of many two operands is 1 and the opposite one is zero. This feels trickier at first, however, if we expect this implies the 2 operands must be completely different for the outcome to be 1 (in any other case it is zero), we come upon the best arithmetic operation to make use of inside calc(): subtraction!

If –k and –i are equal, then subtracting –i from –k provides us zero. In any other case, if we now have –k: zero, –i: 1, the results of the identical subtraction is -1; if we now have –k: 1, –i: zero, the result’s 1.

Shut, however not fairly! We get the outcome we wish in three out of 4 eventualities, however we have to get 1, not -1 within the –k: zero, –i: 1 situation.

Nonetheless, one factor that -1, zero and 1 have in frequent is that multiplying them with themselves provides us their absolute worth (which is 1 for each -1 and 1). So the precise resolution is to multiply this distinction with itself:

–xor: calc((var(–k) – var(–i))*(var(–k) – var(–i)))

Testing every of our 4 doable eventualities, we now have:

for –k: zero, –i: zero, we now have that –xor is zero ((zero – zero)*(zero – zero) = zero*zero)
for –k: zero, –i: 1, we now have that –xor is 1 ((zero – 1)*(zero – 1) = -1*-1)
for –k: 1, –i: zero, we now have that –xor is 1 ((1 – zero)*(1 – zero) = 1*1)
for –k: 1, –i: 1, we now have that –xor is zero ((1 – 1)*(1 – 1) = zero*zero)

Why: Use circumstances

Let’s have a look at a few examples that make use of logical operations in CSS. Be aware that I will not element different features of those demos as they’re outdoors the scope of this explicit article.

Cover disabled panel solely on small screens

It is a use case I got here throughout whereas engaged on an interactive demo that lets customers management varied parameters to vary a visible outcome. For extra educated customers, there’s additionally a panel of superior controls that is disabled by default. It could, nonetheless, be enabled with a purpose to get entry to manually controlling much more parameters.

Since this demo is meant to be responsive, the format adjustments with the viewport. We additionally don’t need issues to get crammed on smaller screens if we will keep away from it, so there is no level in exhibiting the superior controls in the event that they’re disabled and we’re within the slender display case.

The screenshot collage beneath exhibits the outcomes we get for every the 4 doable eventualities.

Collage of the doable circumstances.

So let’s have a look at what this implies when it comes to CSS!

First off, on the , we use a change that goes from zero within the slender display case to 1 within the large display case. We additionally change the flex-direction this manner (if you’d like a extra detailed rationalization of how this works, take a look at my second article on DRY switching with CSS variables).

physique
–k: var(–wide, zero);
show: flex;
flex-direction: var(–wide, column);

@media (orientation: panorama)

We then have a second change on the superior controls panel. This second change is zero if the checkbox is unchecked and 1 if the checkbox is :checked. With the assistance of this change, we give our superior controls panel a disabled look (through a filter chain) and we additionally disable it (through pointer-events). Right here, not turns out to be useful, as we need to lower the distinction and the opacity within the disabled case:

.superior
–i: var(–enabled, zero);
–noti: calc(1 – var(–i));
filter:
distinction(calc(1 – var(–noti)*.9))
opacity(calc(1 – var(–noti)*.7));
pointer-events: var(–enabled, none);

[id=’toggle’]:checked ~ & –enabled: 1

We would like the superior controls panel to remain expanded if we’re within the large display case (so if –k is 1), no matter whether or not the checkbox is :checked or not, or if the checkbox is :checked (so if –i is 1), no matter whether or not we’re within the large display case or not.

That is exactly the or operation!

So we compute an –or variable:

.superior
/* identical as earlier than */
–or: calc(1 – (1 – var(–k))*(1 – var(–i)));

If this –or variable is zero, this implies we’re within the slender display case and our checkbox is unchecked, so we need to zero the peak of the superior controls panel and in addition its vertical margin:

.superior

This provides us the specified outcome (stay demo).

Use the identical formulation to place a number of faces of a 3D form

It is a use case I got here throughout whereas engaged on the private undertaking of CSS-ing the Johnson solids this summer time.

Let’s check out one among these shapes, for instance, the gyroelongated pentagonal rotunda (J25), with a purpose to see how logical operations are helpful right here.

The form we need to get.

This form is made up out of a pentagonal rotunda with out the large decagonal base and a decagonal antiprism with out its high decagon. The interactive demo beneath exhibits how these two elements could be constructed by folding their nets of faces into 3D after which joined to present us the form we wish.

See the Pen by thebabydino (@thebabydino) on CodePen.

As it may be seen above, the faces are both part of the antiprism or part of the rotunda. That is the place we introduce our first change variable –i. That is zero for the faces which can be part of the antiprism and 1 for the faces which can be part of the rotunda. The antiprism faces have a category of .mid as a result of we will add one other rotunda to the opposite antiprism base after which the antiprism could be within the center. The rotunda faces have a category of .cup as a result of this half does seem like a espresso cup… with no deal with!

The rotunda appears to be like like an the wrong way up up cup with no deal with..mid
.cup

Focusing solely on the lateral faces, these can have a vertex pointing up or down. That is the place we introduce our second variable –k. That is zero if they’ve a vertex pointing up (such faces have a .dir class) and 1 in the event that they’re reversed and have a vertex pointing down (these faces have a category of .rev)

.dir –k: zero
.rev –k: 1

The antiprism has 10 lateral faces (all triangles) pointing up, every hooked up to an fringe of its decagonal base that is additionally a base for the compound form. It additionally has 10 lateral faces (all triangles as properly) pointing down, every hooked up to an fringe of its different decagonal base (the one which’s additionally the decagonal base of the rotunda and is due to this fact not a base for the compound form).

The rotunda has 10 lateral faces pointing up, alternating triangles and pentagons, every hooked up to the decagonal base that is additionally a base for the antiprism (so it is not a base for the compound form as properly). It additionally has 5 lateral faces, all triangles, pointing down, every hooked up to an fringe of its pentagonal base.

The interactive demo beneath permits us to raised see every of those 4 teams of faces by highlighting solely one after the other. You should utilize the arrows on the backside to choose which group of faces will get highlighted. You too can allow the rotation across the y axis and alter the form’s tilt.

See the Pen by thebabydino (@thebabydino) on CodePen.

As beforehand talked about, the lateral faces could be both triangles or pentagons:

.s3gon
.s5gon –p: 1

Since all of their lateral faces (.lat) of each the antiprism and the rotunda have one edge in frequent with one of many two base faces of every form, we name these frequent edges the bottom edges of the lateral faces.

The interactive demo beneath highlights these edges, their finish factors and their mid factors and permits viewing the shapes from varied angles due to the auto-rotations across the y axis which could be began/ paused at any second and to the handbook rotations across the x axis which could be managed through the sliders.

See the Pen by thebabydino (@thebabydino) on CodePen.

So as to make issues simpler for ourselves, we set the transform-origin of the .lat faces on the center of their base edges (backside horizontal edges).

SVG illustration.Highlighting the bottom edges and their midpoints (stay).

We additionally be sure that we place these faces corresponding to to have these midpoints lifeless in the midst of the scene factor containing our whole 3D form.

Having the transform-origin coincide with the midpoint the bottom edge signifies that any rotation we carry out on a face goes to occur across the midpoint of its base edge, as illustrated by the interactive demo beneath:

See the Pen by thebabydino (@thebabydino) on CodePen.

We place our lateral faces the place we wish them to be in 4 steps:

We rotate them round their y axis such that their base edges are actually parallel to their closing positions. (This additionally rotates their native system of coordinates — the z axis of a component at all times factors within the route that factor faces.)
We translate them such that their base edges coincide with their closing positions (alongside the sides of the bottom faces of the 2 elements).
If they should have a vertex pointing down, we rotate them round their z axis by half a flip.
We rotate them round their x axis into their closing positions

These steps are illustrated by the interactive demo beneath, the place you’ll be able to undergo them and in addition rotate your complete form (utilizing the play/pause button for the y axis rotation and the slider for the x axis rotation).

See the Pen by thebabydino (@thebabydino) on CodePen.

The y axis rotation worth is primarily based on the face indices and fewer on our change variables, although it relies on these as properly.

The construction is as follows:

– var n = 5; //- variety of edges/ vertices of small base

part.scene
//- 3D form factor
.s3d
//- the faces, every a 2D form factor (.s2d)

//- lateral (.lat) antiprism (.mid) faces,
//- first half pointing up (.dir), others pointing down (.rev)
//- all of them being triangles (.s3gon)
– for(var j = zero; j < four*n; j++) .s2d.mid.lat.s3gon(class=j < 2*n ? 'dir' : 'rev') //- lateral (.lat) rotunda (.cup) faces that time up (.dir), //- each triangles (.s3gon) and pentagons (.s5gon) - for(var j = zero; j < n; j++) .s2d.cup.lat.s3gon.dir .s2d.cup.lat.s5gon.dir //- lateral (.lat) rotunda (.cup) faces that time down (.rev) //- all of them triangles (.s3gon) - for(var j = zero; j < n; j++) .s2d.cup.lat.s3gon.rev //- base faces, //- one for the antiprism (.mid), //- the opposite for the rotunda (.cup) .s2d.mid.base(class=`s$2*ngon`) .s2d.cup.base(class=`s$ngon`)

Which provides us the next HTML:

This implies faces zero… 9 are the 10 lateral antiprism faces pointing up, faces 10… 19 are the 10 lateral antiprism faces pointing down, faces 20… 29 are the 10 lateral rotunda faces pointing up and faces 30… 34 are the 5 lateral rotunda faces pointing down.

So what we do right here is ready an index –idx on the lateral faces.

$n: 5; // variety of edges/ vertices of small base

.lat
@for $i from zero to 2*$n

This index begins at zero for every group of faces, which implies the indices for faces zero… 9, 10… 19 and 20… 29 go from zero by means of 9, whereas the indices for faces 30… 34 go from zero by means of four. Nice, but when we simply multiply these indices with the bottom angle1 of the frequent decagon to get the y axis rotation we wish at this step:

–ay: calc(var(–idx)*#);

remodel: rotatey(var(–ay))

…then we get the next closing outcome. I am exhibiting the ultimate outcome right here as a result of it’s kind of tough to see what’s fallacious by wanting on the intermediate outcome we get after solely making use of the rotation across the y axis.

See the Pen by thebabydino (@thebabydino) on CodePen.

That is… not fairly what we had been going for!

So let’s have a look at what issues the above outcome has and how one can resolve them with the assistance of our change variables and boolean operations on them.

The primary challenge is that the lateral antiprism faces pointing up must be offset by half of an everyday decagon’s base angle. This implies including or subtracting .5 from –idx earlier than multiplying with the bottom angle, however just for these faces.

See the Pen by thebabydino (@thebabydino) on CodePen.

The faces we need to goal are the faces for which each of –i and –k are zero, so what we want right here is multiply the results of their nor with .5:

–nor: calc((1 – var(–k))*(1 – var(–i)));
–j: calc(var(–idx) + var(–nor)*.5);
–ay: calc(var(–j)*#);

remodel: rotatey(var(–ay));

The second challenge is that the lateral rotunda faces pointing down are usually not distributed as they need to be, such that every of them has a base edge in frequent with the bottom pentagon and the vertex opposing the bottom in frequent with the triangular rotunda faces pointing up. This implies multiplying –idx by 2, however just for these faces.

See the Pen by thebabydino (@thebabydino) on CodePen.

What we’re focusing on now are the faces for which each –i and –k are 1 (so the faces for which the results of the and operation is 1), so what we want is to multiply –idx with 1 plus their and:

–and: calc(var(–k)*var(–i));
–nor: calc((1 – var(–k))*(1 – var(–i)));
–j: calc((1 + var(–and))*var(–idx) + var(–nor)*.5);
–ay: calc(var(–j)*#);

remodel: rotatey(var(–ay));

The following step is the interpretation for which we use translate3d(). We do not transfer any of our faces left or proper, so the worth alongside the x axis is at all times zero. We do transfer them nonetheless vertically (alongside the y axis) and ahead (alongside the z axis)

Vertically, we wish the cup faces that can later get rotated to level right down to have their base edge within the aircraft of the small (pentagonal) base of the cup (and of the compound form). This implies the faces for which –i is 1 and –k is 1 get moved up (unfavourable route) by half the overall peak of the compound form (a complete peak which we now have computed to be $h). So we want the and operation right here.

// identical as earlier than
–and: calc(var(–i)*var(–k));
–y: calc(var(–and)*#-.5*$h);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y, zero), var(–z, zero));

We additionally need all the opposite cup faces in addition to the antiprism faces that can ultimately level right down to have their base edge within the frequent aircraft between the cup and the antiprism. This implies the faces for which –i is 1 and –k is zero in addition to the faces for which –i is zero and –k is 1 get translated down (optimistic route) by half the peak of the compound form after which again up (unfavourable route) by the peak of the antiprism ($h-mid). And what have you learnt, that is the xor operation!

// identical as earlier than
–xor: calc((var(–k) – var(–i))*(var(–k) – var(–i)));
–and: calc(var(–i)*var(–k));
–y: calc(var(–xor)*# –
var(–and)*#);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y, zero), var(–z, zero));

Lastly, we wish the antiprism faces that can stay pointing as much as be within the backside base aircraft of the compound form (and of the antiprism). This implies the faces for which –i is zero and –k is zero get translated down (optimistic route) by half the overall peak of the compound form. So what we use right here is the nor operation!

// identical as earlier than
–nor: calc((1 – var(–k))*(1 – var(–i)));
–xor: calc((var(–k) – var(–i))*(var(–k) – var(–i)));
–and: calc(var(–i)*var(–k));

–y: calc(var(–nor)*# +
var(–xor)*# –
var(–and)*#);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y, zero), var(–z, zero));

See the Pen by thebabydino (@thebabydino) on CodePen.

Alongside the z route, we need to transfer the faces such that their base edges coincide with the sides of the bottom faces of the compound form or the sides of the frequent base (which isn’t a face of the compound form) shared by the 2 3D elements. For the highest faces of the cup (which we later rotate to level down), the position is on the sides of a pentagon, whereas for all the opposite faces of the compound form, the position is on the sides of a decagon.

This implies the faces for which –i is 1 and –k is 1 get translated ahead by the inradius of the pentagonal base whereas all the opposite faces get translated ahead by the inradius of a decagonal base. So the operations we want listed below are and and nand!

// identical as earlier than
–and: calc(var(–i)*var(–k));
–nand: calc(1 – var(–and));
–z: calc(var(–and)*# + var(–nand)*#$ri10gon);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y, zero), var(–z, zero));

See the Pen by thebabydino (@thebabydino) on CodePen.

Subsequent, we need to make all .rev (for which –k is 1) faces level down. That is fairly easy and does not require any logical operation, we simply want so as to add a half a flip rotation across the z axis to the remodel chain, however just for the faces for which –k is 1:

// identical as earlier than
–az: calc(var(–k)*.5turn);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y), var(–z))
rotate(var(–az));

See the Pen by thebabydino (@thebabydino) on CodePen.

The pentagonal faces (for which –p is 1) are then all rotated across the x axis by a sure angle:

–ax: calc(var(–p)*#);

Within the case of the triangular faces (for which –p is zero, which means we have to use –notp), we now have a sure rotation angle for the faces of the antiprism ($ax3-mid), one other angle for the faces of the rotunda that time up ($ax3-cup-dir) and one more angle for the rotunda faces pointing down ($ax3-cup-red).

The antiprism faces are these for which –i is zero, so we have to multiply their corresponding angle worth with –noti right here. The rotunda faces are these for which –i is 1, and out of those, those pointing up are these for which –k is zero and those pointing down are these for which –k is 1.

–notk: calc(1 – var(–k));
–noti: calc(1 – var(–i));
–notp: calc(1 – var(–p));

–ax: calc(var(–notp)*(var(–noti)*# +
var(–i)*(var(–notk)*# + var(–k)*#$ax3-cup-rev)) +
var(–p)*#);

remodel: rotatey(var(–ay))
translate3d(zero, var(–y), var(–z))
rotate(var(–az))
rotatex(var(–ax));

This provides us the ultimate outcome!

See the Pen by thebabydino (@thebabydino) on CodePen.

1For any common polygon (corresponding to any of the faces of our shapes), the arc corresponding to at least one edge, in addition to the angle between the circumradii to this edge’s ends (our base angle) is a full circle (360°) over the variety of edges. Within the case of an equilateral triangle, the angle is 360°/three = 120°. For an everyday pentagon, the angle is 360°/5 = 72°. For an everyday decagon, the angle is 360°/10 = 36°. ↪️

See the Pen by thebabydino (@thebabydino) on CodePen.

Leave a Reply