Utilizing Absolute Worth, Signal, Rounding and Modulo in CSS At this time

No Comments

For fairly some time now, the CSS spec has included lots of actually helpful mathematical capabilities, corresponding to trigonometric capabilities (sin(), cos(), tan(), asin(), acos(), atan(), atan2()), exponential capabilities (pow(), exp(), sqrt(), log(), hypot()), sign-related capabilities (abs(), signal()) and stepped worth capabilities (spherical(), mod(), rem()).

Nonetheless, these will not be but applied in any browser, so this text goes to indicate how, utilizing CSS options we have already got, we will compute the values that abs(), signal(), spherical() and mod() ought to return. After which we’ll see what cool issues this permits us to construct at this time.

A couple of of the issues these capabilities permit us to make.

Be aware that none of those methods have been ever meant to work in browsers from again within the days when dinosaurs roamed the web. Some methods rely on the browser supporting the flexibility to register customized properties (utilizing @property), which implies they’re restricted to Chromium for now.

The computed equivalents

–abs

We are able to get this by utilizing the brand new CSS max() perform, which is already applied within the present variations of all main browsers.

Let’s say we’ve got a customized property, –a. We don’t know whether or not that is optimistic or unfavorable and we need to get its absolute worth. We do that by selecting the utmost between this worth and its additive inverse:

–abs: max(var(–a), -1*var(–a));

If –a is optimistic, this implies it’s higher than zero, and multiplying it with -1 offers us a unfavorable quantity, which is at all times smaller than zero. That, in flip, is at all times smaller than the optimistic –a, so the end result returned by max() is the same as var(–a).

If –a is unfavorable, this implies it’s smaller than zero, and that multiplying it by -1 offers us a optimistic quantity, which is at all times larger than zero, which, in flip, is at all times larger than the unfavorable –a. So, the end result returned by max() is the same as -1*var(–a).

–sign

That is one thing we will get utilizing the earlier part because the signal of a quantity is that quantity divided by its absolute worth:

–abs: max(var(–a), -1*var(–a));
–sign: calc(var(–a)/var(–abs));

An important factor to notice right here is that this solely works if –a is unitless, as we can’t divide by a quantity with a unit inside calc().

Additionally, if –a is 0, this resolution works provided that we register –sign (that is solely supported in Chromium browsers at this level) with an initial-value of 0:

@property –sign {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false /* or true relying on context */
}

It is because –a, being 0, additionally makes –abs compute to 0 — and dividing by 0 is invalid in CSS calc() — so we want to verify –sign will get reset to 0 on this state of affairs. Remember that this doesn’t occur if we merely set it to 0 within the CSS previous to setting it to the calc() worth and we don’t register it:

–abs: max(var(–a), -1*var(–a));
–sign: 0; /* would not assist */
–sign: calc(var(–a)/var(–abs));

In observe, I’ve additionally usually used the next model for integers:

–sign: clamp(-1, var(–a), 1);

Right here, we’re utilizing a clamp() perform. This takes three arguments: a minimal allowed worth -1, a most popular worth var(–a) and a most allowed worth, 1. The worth returned is the popular worth so long as it’s between the decrease and higher bounds and the restrict that will get exceeded in any other case.

If –a is a unfavorable integer, this implies it’s smaller or equal to -1, the decrease sure (or the minimal allowed worth) of our clamp() perform, so the worth returned is -1. If it’s a optimistic integer, this implies it’s higher or equal to 1, the higher sure (or the utmost allowed worth) of the clamp() perform, so the worth returned is 1. And eventually, if –a is 0, it’s between the decrease and higher limits, so the perform returns its worth (0 on this case).

This technique has the benefit of being easier with out requiring Houdini help. That mentioned, word that it solely works for unitless values (evaluating a size or an angle worth with integers like ±1 is like evaluating apples and oranges — it doesn’t work!) which are both precisely 0 or a minimum of as massive as 1 in absolute worth. For a subunitary worth, like -.05, our technique above fails, as the worth returned is -.05, not -1!

My first thought was that we will prolong this method to subunitary values by introducing a restrict worth that’s smaller than the smallest non-zero worth we all know –a can probably take. For instance, let’s say our restrict is .000001 — this might permit us to accurately get -1 because the signal for -.05, and 1 because the signal for .0001!

–lim: .000001;
–sign: clamp(-1*var(–lim), var(–a), var(–lim));

Temani Afif advised an easier model that might multiply –a by a really massive quantity so as to produce a superunitary worth.

–sign: clamp(-1, var(–a)*10000, 1);

I ultimately settled on dividing –a by the restrict worth as a result of it simply feels a bit extra intuitive to see what minimal non-zero worth it gained’t go under.

–lim: .000001;
–sign: clamp(-1, var(–a)/var(–lim), 1);

–round (in addition to –ceil and –floor)

That is one I used to be caught on for some time till I bought a intelligent suggestion for the same drawback from Christian Schaefer. Identical to the case of the signal, this solely works on unitless values and requires registering the –round variable as an <integer> in order that we pressure rounding on no matter worth we set it to:

@property –round {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false /* or true relying on context */
}

.my-elem { –round: var(–a); }

By extension, we will get –floor and –ceil if we subtract or add .5:

@property –floor {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false /* or true relying on context */
}

@property –ceil {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false /* or true relying on context */
}

.my-elem {
–floor: calc(var(–a) – .5);
–ceil: calc(var(–a) + .5)
}

–mod

This builds on the –floor method so as to get an integer quotient, which then permits us to get the modulo worth. Which means that each our values should be unitless.

@property –floor {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false /* or true relying on context */
}

.my-elem {
–floor: calc(var(–a)/var(–b) – .5);
–mod: calc(var(–a) – var(–b)*var(–floor))
}

Use circumstances

What kind of issues can we do with the method? Let’s take a very good take a look at three use circumstances.

Easy symmetry in staggered animations (and never solely!)

Whereas absolutely the worth may help us get symmetrical outcomes for lots of properties, animation-delay and transition-delay are those the place I’ve been utilizing it probably the most, so let’s see some examples of that!

We put –n gadgets inside a container, every of this stuff having an index –i. Each –n and –i are variables we move to the CSS through type attributes.

– let n = 16;

.wrap(type=`–n: ${n}`)
– for(let i = 0; i < n; i++)
.merchandise(type=`–i: ${i}`)

This offers us the next compiled HTML:

<div class=’wrap’ type=’–n: 16′>
<div class=’merchandise’ type=’–i: 0′></div>
<div class=’merchandise’ type=’–i: 1′></div>
<!– extra such gadgets –>
</div>

We set a couple of types such that the gadgets are specified by a row and are sq. with a non-zero edge size:

$r: 2.5vw;

.wrap {
show: flex;
justify-content: space-evenly;
}

.merchandise { padding: $r; }

The end result thus far.

Now we add two units of keyframes to animate a scaling remodel and a box-shadow. The primary set of keyframes, develop, makes our gadgets scale up from nothing at 0% to full measurement at 50%, after which they keep at their full measurement till the tip. The second set of keyframes, soften, exhibits us the gadgets having inset field shadows that cowl them totally as much as the halfway level within the animation (at 50%). That’s additionally when the gadgets attain full measurement after rising from nothing. Then the unfold radius of those inset shadows shrinks till it will get all the way down to nothing at 100%.

$r: 2.5vw;

.merchandise {
padding: $r;
animation: a $t infinite;
animation-name: develop, soften;
}

@keyframes develop {
0% { remodel: scale(0); }
50%, 100% { remodel: none; }
}

@keyframes soften {
0%, 50% { box-shadow: inset 0 0 0 $r; }
100% { box-shadow: inset 0 0; }
}

The bottom animation (stay demo).

Now comes the fascinating half! We compute the center between the index of the primary merchandise and that of the final one. That is the arithmetic imply of the 2 (since our indices are zero-based, the primary and final are 0 and n – 1 respectively):

–m: calc(.5*(var(–n) – 1));

We get absolutely the worth, –abs, of the distinction between this center, –m, and the merchandise index, –i, then use it to compute the animation-delay:

–abs: max(var(–m) – var(–i), var(–i) – var(–m));
animation: a $t calc(var(–abs)/var(–m)*#{$t}) infinite backwards;
animation-name: develop, soften;

Absolutely the worth ,–abs, of the distinction between the center, –m, and the merchandise index, –i, will be as small as 0 (for the center merchandise, if –n is odd) and as massive as –m (for the tip gadgets). This implies dividing it by –m at all times offers us a worth within the [0, 1] interval, which we then multiply with the animation length $t to make sure each merchandise has a delay between 0s and the animation-duration.

Be aware that we’ve additionally set animation-fill-mode to backwards. Since most gadgets will begin the animations later, this tells the browser to maintain them with the types within the 0% keyframes till then.

On this specific case, we wouldn’t see any distinction with out it both as a result of, whereas the gadgets can be at full measurement (not scaled to nothing like within the 0% keyframe of the develop animation), they might additionally haven’t any box-shadow till they begin animating. Nonetheless, in lots of different circumstances, it does make a distinction and we shouldn’t overlook about it.

One other risk (one which doesn’t contain setting the animation-fill-mode) can be to make sure the animation-delay is at all times smaller or at most equal to 0 by subtracting a full animation-duration out of it.

–abs: max(var(–m) – var(–i), var(–i) – var(–m));
animation: a $t calc((var(–abs)/var(–m) – 1)*#{$t}) infinite;
animation-name: develop, soften;

Each choices are legitimate, and which one you employ is dependent upon what you favor to occur on the very starting. I tend to go for unfavorable delays as a result of they make extra sense when recording the looping animation to make a gif just like the one under, which illustrates how the animation-delay values are symmetrical with respect to the center.

The staggered looping animation.

For a visible comparability between the 2 choices, you may rerun the next demo to see what occurs on the very starting.

CodePen Embed Fallback

A fancier instance can be the next:

Navigation hyperlinks sliding up after which again down with a delay proportional to how far they’re from the chosen one.

Right here, each one of many –n navigation hyperlinks and corresponding recipe articles have an index –idx. At any time when a navigation hyperlink is hovered or targeted, its –idx worth is learn and set to the present index, –k, on the physique. If none of this stuff is hovered or targeted, –k will get set to a worth exterior the [0, n) interval (e.g. -1).

The absolute value, –abs, of the difference between –k and a link’s index, –idx, can tell us whether that’s the currently selected (hovered or focused) item. If this absolute value is 0, then our item is the currently selected one (i.e. –not-sel is 0 and –sel is 1). If this absolute value is bigger than 0, then our item is not the currently selected one (i.e. –not-sel is 1 and –sel is 0).

Given both –idx and –k are integers, it results that their difference is also an integer. This means the absolute value, –abs, of this difference is either 0 (when the item is selected), or bigger or equal to 1 (when the item is not selected).

When we put all of this into code, this is what we get:

–abs: Max(var(–k) – var(–idx), var(–idx) – var(–k));
–not-sel: Min(1, var(–abs));
–sel: calc(1 – var(–not-sel));

The –sel and –not-sel properties (which are always integers that always add up to 1) determine the size of the navigation links (the width in the wide screen scenario and the height in the narrow screen scenario), whether they’re greyscaled or not and whether or not their text content is hidden. This is something we won’t get into here, as it is outside the scope of this article and I’ve already explained in a lot of detail in a previous one.

What is relevant here is that, when a navigation link is clicked, it slides out of sight (up in the wide screen case, and left in the narrow screen case), followed by all the others around it, each with a transition-delay that depends on how far they are from the one that was clicked (that is, on the absolute value, –abs, of the difference between their index, –idx, and the index of the currently selected item, –k), revealing the corresponding recipe article. These transition-delay values are symmetrical with respect to the currently selected item.

transition: transform 1s calc(var(–abs)*.05s);

The actual transition and delay are actually a bit more complex because more properties than just the transform get animated and, for transform in particular, there’s an additional delay when going back from the recipe article to the navigation links because we wait for the <article> element to disappear before we let the links slide down. But what were’re interested in is that component of the delay that makes the links is closer to the selected one start sliding out of sight before those further away. And that’s computed as above, using the –abs variable.

You can play with the interactive demo below.

CodePen Embed Fallback

Things get even more interesting in 2D, so let’s now make our row a grid!

We start by changing the structure a bit so that we have 8 columns and 8 rows (which means we have 8·8 = 64 items in total on the grid).

– let n = 8;
– let m = n*n;

style
– for(let i = 0; i < n; i++)
| .item:nth-child(#{n}n + #{i + 1}) { –i: #{i} }
| .item:nth-child(n + #{n*i + 1}) { –j: #{i} }
.wrap(style=`–n: ${n}`)
– for(let i = 0; i < m; i++)
.item

The above Pug code compiles to the following HTML:

<style>
.item:nth-child(8n + 1) { –i: 0 } /* items on 1st column */
.item:nth-child(n + 1) { –j: 0 } /* items starting from 1st row */
.item:nth-child(8n + 2) { –i: 1 } /* items on 2nd column */
.item:nth-child(n + 9) { –j: 1 } /* items starting from 2nd row */
/* 6 more such pairs */
</style>
<div class=’wrap’ style=’–n: 8′>
<div class=’item’></div>
<div class=’item’></div>
<!– 62 more such items –>
</div>

Just like the previous case, we compute a middle index, –m, but since we’ve moved from 1D to 2D, we now have two differences in absolute value to compute, one for each of the two dimensions (one for the columns, –abs-i, and one for the rows, –abs-j).

–m: calc(.5*(var(–n) – 1));
–abs-i: max(var(–m) – var(–i), var(–i) – var(–m));
–abs-j: max(var(–m) – var(–j), var(–j) – var(–m));

We use the exact same two sets of @keyframes, but the animation-delay changes a bit, so it depends on both –abs-i and –abs-j. These absolute values can be as small as 0 (for tiles in the dead middle of the columns and rows) and as big as –m (for tiles at the ends of the columns and rows), meaning that the ratio between either of them and –m is always in the [0, 1] interval. This implies the sum of those two ratios is at all times within the [0, 2] interval. If we need to cut back it to the [0, 1] interval, we have to divide it by 2 (or multiply by .5, similar factor).

animation-delay: calc(.5*(var(–abs-i)/var(–m) + var(–abs-j)/var(–m))*#{$t});

This offers us delays which are within the [0s, $t] interval. We are able to take the denominator, var(–m), out of the parenthesis to simplify the above method a bit:

animation-delay: calc(.5*(var(–abs-i) + var(–abs-j))/var(–m)*#{$t});

Identical to the earlier case, this makes grid gadgets begin animating later the additional they’re from the center of the grid. We must always use animation-fill-mode: backwards to make sure they keep within the state specified by the 0% keyframes till the delay time has elapsed they usually begin animating.

Alternatively, we will subtract one animation length $t from all delays to verify all grid gadgets have already began their animation when the web page hundreds.

animation-delay: calc((.5*(var(–abs-i) + var(–abs-j))/var(–m) – 1)*#{$t});

This offers us the next end result:

The staggered 2D animation (stay demo).

Let’s now see a couple of extra fascinating examples. We gained’t be going into particulars in regards to the “how” behind them because the symmetrical worth method works precisely the identical as for the earlier ones and the remaining is exterior the scope of this text. Nonetheless, there’s a hyperlink to a CodePen demo within the caption for every of the examples under, and most of those Pens additionally include a recording that exhibits me coding them from scratch.

Within the first instance, every grid merchandise is made up of two triangles that shrink all the way down to nothing at reverse ends of the diagonal they meet alongside after which develop again to full measurement. Since that is an alternating animation, we let the delays to stretch throughout two iterations (a standard one and a reversed one), which implies we don’t divide the sum of ratios in half anymore and we subtract 2 to make sure each merchandise has a unfavorable delay.

animation: s $t ease-in-out infinite alternate;
animation-delay: calc(((var(–abs-i) + var(–abs-j))/var(–m) – 2)*#{$t});

Grid wave: pulsing triangles (stay demo)

Within the second instance, every grid merchandise has a gradient at an angle that animates from 0deg to 1turn. That is doable through Houdini as defined in this text in regards to the state of animating gradients with CSS.

Discipline wave: cell gradient rotation (stay demo)

The third instance may be very related, besides the animated angle is utilized by a conic-gradient as an alternative of a linear one and in addition by the hue of the primary cease.

Rainbow hour wave (stay demo)

Within the fourth instance, every grid cell comprises seven rainbow dots that oscillate up and down. The oscillation delay has a part that is dependent upon the cell indices in the very same method because the earlier grids (the one factor that’s totally different right here is the variety of columns differs from the variety of rows, so we have to compute two center indices, one alongside every of the 2 dimensions) and a part that is dependent upon the dot index, –idx, relative to the variety of dots per cell, –n-dots.

–k: calc(var(–idx)/var(–n-dots));
–mi: calc(.5*(var(–n-cols) – 1));
–abs-i: max(var(–mi) – var(–i), var(–i) – var(–mi));
–mj: calc(.5*(var(–n-rows) – 1));
–abs-j: max(var(–mj) – var(–j), var(–j) – var(–mj));
animation-delay:
calc((var(–abs-i)/var(–mi) + var(–abs-j)/var(–mj) + var(–k) – 3)*#{$t});

Rainbow dot wave: dot oscillation (stay demo)

Within the fifth instance, the tiles making up the dice faces shrink and transfer inwards. The animation-delay for the highest face is computed precisely as in our first 2D demo.

Breathe into me: neon waterfall (stay demo and a earlier iteration)

Within the sixth instance, we’ve got a grid of columns oscillating up and down.

Column wave (stay demo)

The animation-delay isn’t the one property we will set to have symmetrical values. We are able to additionally do that with the gadgets’ dimensions. Within the seventh instance under, the tiles are distributed round half a dozen rings ranging from the vertical (y) axis and are scaled utilizing an element that is dependent upon how far they’re from the highest level of the rings. That is mainly the 1D case with the axis curved on a circle.

Round grid soften (stay demo)

The eighth instance exhibits ten arms of baubles that wrap round an enormous sphere. The scale of those baubles is dependent upon how far they’re from the poles, the closest ones being the smallest. That is achieved by computing the center index, –m, for the dots on an arm and absolutely the worth, –abs, of the distinction between it and the present bauble index, –j, then utilizing the ratio between this absolute worth and the center index to get the sizing issue, –f, which we then use when setting the padding.

–m: calc(.5*(var(–n-dots) – 1));
–abs: max(var(–m) – var(–j), var(–j) – var(–m));
–f: calc(1.05 – var(–abs)/var(–m));
padding: calc(var(–f)*#{$r});

Journey contained in the sphere (stay demo)

Totally different types for gadgets earlier than and after a sure (chosen or center) one

Let’s say we’ve got a bunch of radio buttons and labels, with the labels having an index set as a customized property, –i. We would like the labels earlier than the chosen merchandise to have a inexperienced background, the label of the chosen merchandise to have a blue background and the remainder of the labels to be gray. On the physique, we set the index of the at present chosen choice as one other customized property, –k.

– let n = 8;
– let ok = Math.spherical((n – 1)*Math.random());

physique(type=`–k: ${ok}`)
– for(let i = 0; i < n; i++)
– let id = `r${i}`;
enter(kind=’radio’ identify=’r’ id=id checked=i===ok)
label(for=id type=`–i: ${i}`) Choice ##{i}

This compiles to the next HTML:

<physique type=’–k: 1′>
<enter kind=’radio’ identify=’r’ id=’r0’/>
<label for=’r0′ type=’–i: 0′>Choice #0</label>
<enter kind=’radio’ identify=’r’ id=’r1′ checked=’checked’/>
<label for=’r1′ type=’–i: 1′>Choice #1</label>
<enter kind=’radio’ identify=’r’ id=’r2’/>
<label for=’r2′ type=’–i: 2′>Choice #2</label>
<!– extra choices –>
</physique>

We set a couple of structure and prettifying types, together with a gradient background on the labels that creates three vertical stripes, every occupying a 3rd of the background-size (which, for now, is simply the default 100%, the total factor width):

$c: #6daa7e, #335f7c, #6a6d6b;

physique {
show: grid;
grid-gap: .25em 0;
grid-template-columns: repeat(2, max-content);
align-items: heart;
font: 1.25em/ 1.5 ubuntu, trebuchet ms, sans-serif;
}

label {
padding: 0 .25em;
background:
linear-gradient(90deg,
nth($c, 1) 33.333%,
nth($c, 2) 0 66.667%,
nth($c, 3) 0);
coloration: #fff;
cursor: pointer;
}

The end result thus far.

From the JavaScript, we replace the worth of –k each time we choose a distinct choice:

addEventListener(‘change’, e => {
let _t = e.goal;

doc.physique.type.setProperty(‘–k’, +_t.id.substitute(‘r’, ”))
})

Now comes the fascinating half! For our label components, we compute the signal, –sgn, of the distinction between the label index, –i, and the index of the at present chosen choice, –k. We then use this –sgn worth to compute the background-position when the background-size is ready to 300% — that’s, thrice the label’s width as a result of we could have of three doable backgrounds: one for the case when the label is for an choice earlier than the chosen one, a second for the case when the label is for the chosen choice, and a 3rd for the case when the label is for an choice after the chosen one.

–sgn: clamp(-1, var(–i) – var(–k), 1);
background:
linear-gradient(90deg,
nth($c, 1) 33.333%,
nth($c, 2) 0 66.667%,
nth($c, 3) 0)
calc(50%*(1 + var(–sgn)))/ 300%

If –i is smaller than –k (the case of a label for an choice earlier than the chosen one), then –sgn is -1 and the background-position computes to 50%*(1 + -1) = 50%*0 = 0%, that means we solely see the primary vertical stripe (the inexperienced one).

If –i is equal –k (the case of the label for the chosen choice), then –sgn is 0 and the background-position computes to 50%*(1 + 0) = 50%*1 = 50%, so we solely see the vertical stripe within the center (the blue one).

If –i is bigger than –k (the case of a label for an choice after the chosen one), then –sgn is 1 and the background-position computes to 50%*(1 + 1) = 50%*2 = 100%, that means we solely see the final vertical stripe (the gray one).

CodePen Embed Fallback

A extra aesthetically interesting instance can be the next navigation the place the vertical bar is on the facet closest to the chosen choice and, for the chosen one, it spreads throughout all the factor.

This makes use of a construction that’s just like that of the earlier demo, with radio inputs and labels for the navigation gadgets. The transferring “background” is definitely an ::after pseudo-element whose translation worth is dependent upon the signal, –sgn. The textual content is a ::earlier than pseudo-element whose place is meant to be in the course of the white space, so its translation worth additionally is dependent upon –sgn.

/* related types */
label {
–sgn: clamp(-1, var(–k) – var(–i), 1);

&::earlier than {
remodel: translate(calc(var(–sgn)*-.5*#{$pad}))
}
&::after {
remodel: translate(calc(var(–sgn)*(100% – #{$pad})))
}
}

CodePen Embed Fallback

Let’s now rapidly take a look at a couple of extra demos the place computing the signal (and perhaps absolutely the worth as nicely) turns out to be useful.

First up, we’ve got a sq. grid of cells with a radial-gradient whose radius shrinks from masking all the cell to nothing. This animation has a delay computed as defined within the earlier part. What’s new right here is that the coordinates of the radial-gradient circle rely on the place the cell is positioned with respect to the center of the grid — that’s, on the indicators of the variations between the column –i and row –j indices and the center index, –m.

/* related CSS */
$t: 2s;

@property –p {
syntax: ‘<length-percentage>’;
initial-value: -1px;
inherits: false;
}

.cell {
–m: calc(.5*(var(–n) – 1));
–dif-i: calc(var(–m) – var(–i));
–abs-i: max(var(–dif-i), -1*var(–dif-i));
–sgn-i: clamp(-1, var(–dif-i)/.5, 1);
–dif-j: calc(var(–m) – var(–j));
–abs-j: max(var(–dif-j), -1*var(–dif-j));
–sgn-j: clamp(-1, var(–dif-j)/.5, 1);
background:
radial-gradient(circle
at calc(50% + 50%*var(–sgn-i)) calc(50% + 50%*var(–sgn-j)),
currentcolor var(–p), clear calc(var(–p) + 1px))
nth($c, 2);
animation-delay:
calc((.5*(var(–abs-i) + var(–abs-j))/var(–m) – 1)*#{$t});
}

@keyframes p { 0% { –p: 100%; } }

Sinking feeling (stay demo)

Then we’ve got a double spiral of tiny spheres the place each the sphere diameter –d and the radial distance –x that contributes to figuring out the sphere place rely on absolutely the worth –abs of the distinction between every one’s index, –i, and the center index, –m. The signal, –sgn, of this distinction is used to find out the spiral rotation course. This is dependent upon the place every sphere is with respect to the center – that’s, whether or not its index ,–i, is smaller or larger than the center index, –m.

/* related types */
–m: calc(.5*(var(–p) – 1));
–abs: max(calc(var(–m) – var(–i)), calc(var(–i) – var(–m)));
–sgn: clamp(-1, var(–i) – var(–m), 1);
–d: calc(3px + var(–abs)/var(–p)*#{$d}); /* sphere diameter */
–a: calc(var(–k)*1turn/var(–n-dot)); /* angle used to find out sphere place */
–x: calc(var(–abs)*2*#{$d}/var(–n-dot)); /* how removed from spiral axis */
–z: calc((var(–i) – var(–m))*2*#{$d}/var(–n-dot)); /* place with respect to display screen airplane */
width: var(–d); top: var(–d);
remodel:
/* change rotation course by altering x axis course */
scalex(var(–sgn))
rotate(var(–a))
translate3d(var(–x), 0, var(–z))
/* reverse rotation so the sphere is at all times seen from the entrance */
rotate(calc(-1*var(–a)));
/* reverse scaling so lighting on sphere seems to be constant */
scalex(var(–sgn))

No perspective (stay demo)

Lastly, we’ve got a grid of non-square bins with a border. These bins have a masks created utilizing a conic-gradient with an animated begin angle, –ang. Whether or not these bins are flipped horizontally or vertically is dependent upon the place they’re with respect to the center – that’s, on the indicators of the variations between the column –i and row –j indices and the center index, –m. The animation-delay is dependent upon absolutely the values of those variations and is computed as defined within the earlier part. We even have a gooey filter for a nicer “wormy” look, however we gained’t be going into that right here.

/* related CSS */
$t: 1s;

@property –ang {
syntax: ‘<angle>’;
initial-value: 0deg;
inherits: false;
}

.field {
–m: calc(.5*(var(–n) – 1));
–dif-i: calc(var(–i) – var(–m));
–dif-j: calc(var(–j) – var(–m));
–abs-i: max(var(–dif-i), -1*var(–dif-i));
–abs-j: max(var(–dif-j), -1*var(–dif-j));
–sgn-i: clamp(-1, 2*var(–dif-i), 1);
–sgn-j: clamp(-1, 2*var(–dif-j), 1);
remodel: scale(var(–sgn-i), var(–sgn-j));
masks:
repeating-conic-gradient(from var(–ang, 0deg),
crimson 0% 12.5%, clear 0% 50%);
animation: ang $t ease-in-out infinite;
animation-delay:
calc(((var(–abs-i) + var(–abs-j))/var(–n) – 1)*#{$t});
}

@keyframes ang { to { –ang: .5turn; } }

Consumed by worms (stay demo)

Time (and never solely) formatting

Let’s say we’ve got a component for which we retailer a lot of seconds in a customized property, –val, and we need to show this in a mm:ss format, for instance.

We use the ground of the ratio between –val and 60 (the variety of seconds in a minute) to get the variety of minutes and modulo for the variety of seconds previous that variety of minutes. Then we use a intelligent little counter trick to show the formatted time in a pseudo-element.

@property –min {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

code {
–min: calc(var(–val)/60 – .5);
–sec: calc(var(–val) – var(–min)*60);
counter-reset: min var(–min) sec var(–sec);

&::after {
/* so we get the time formatted as 02:09 */
content material:
counter(min, decimal-leading-zero) ‘:’
counter(sec, decimal-leading-zero);
}
}

This works in most conditions, however we encounter an issue when –val is precisely 0. On this case, 0/60 is 0 after which subtracting .5, we get -.5, which will get rounded to what’s the larger adjoining integer in absolute worth. That’s, -1, not 0! This implies our end result will find yourself being -01:60, not 00:00!

Happily, we’ve got a easy repair and that’s to barely alter the method for getting the variety of minutes, –min:

–min: max(0, var(–val)/60 – .5);

There are different formatting choices too, as illustrated under:

/* exhibits time formatted as 2:09 */
content material: counter(min) ‘:’ counter(sec, decimal-leading-zero);

/* exhibits time formatted as 2m9s */
content material: counter(min) ‘m’ counter(sec) ‘s’;

We are able to additionally apply the identical method to format the time as hh:mm:ss (stay take a look at).

@property –hrs {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

@property –min {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

code {
–hrs: max(0, var(–val)/3600 – .5);
–mod: calc(var(–val) – var(–hrs)*3600);
–min: max(0, var(–mod)/60 – .5);
–sec: calc(var(–mod) – var(–min)*60);
counter-reset: hrs var(–hrs) var(–min) sec var(–sec);

&::after {
/* so we get the time formatted as 00:02:09 */
content material:
counter(hrs, decimal-leading-zero) ‘:’
counter(min, decimal-leading-zero) ‘:’
counter(sec, decimal-leading-zero);
}
}

This can be a method I’ve used for styling the output of native vary sliders such because the one under.

Styled vary enter indicating time (stay demo)

Time isn’t the one factor we will use this for. Counter values must be integer values, which implies the modulo trick additionally turns out to be useful for displaying decimals, as within the second slider seen under.

Styled vary inputs, one in all which has a decimal output (stay demo)

A pair extra such examples:

Styled vary inputs, one in all which has a decimal output (stay demo)

Styled vary inputs, one in all which has a decimal output (stay demo)

Much more use circumstances

Let’s say we’ve got a quantity slider with an icon at every finish. Relying on the course we transfer the slider’s thumb in, one of many two icons will get highlighted. That is doable by getting absolutely the worth, –abs, of the distinction between every icon’s signal, –sgn-ico (-1 for the one earlier than the slider, and 1 for the one after the slider), and the signal of the distinction, –sgn-dir, between the slider’s present worth, –val, and its earlier worth, –prv. If that is 0, then we’re transferring within the course of the present icon so we set its opacity to 1. In any other case, we’re transferring away from the present icon, so we hold its opacity at .15.

Which means that, each time the vary enter’s worth modifications, not solely do we have to replace its present worth, –val, on its father or mother, however we have to replace its earlier worth, which is one other customized property, –prv, on the identical father or mother wrapper:

addEventListener(‘enter’, e => {
let _t = e.goal, _p = _t.parentNode;

_p.type.setProperty(‘–prv’, +_p.type.getPropertyValue(‘–val’))
_p.type.setProperty(‘–val’, +_t.worth)
})

The signal of their distinction is the signal of the course, –sgn-dir, we’re stepping into and the present icon is highlighted if its signal, –sgn-ico, and the signal of the course we’re stepping into, –sgn-dir, coincide. That’s, if absolutely the worth, –abs, of their distinction is 0 and, on the similar time, the father or mother wrapper is chosen (it’s both being hovered or the vary enter in it has focus).

[role=’group’] {
–dir: calc(var(–val) – var(–prv));
–sgn-dir: clamp(-1, var(–dir), 1);
–sel: 0; /* is the slider targeted or hovered? Sure 1/ No 0 */

&:hover, &:focus-within { –sel: 1; }
}

.ico {
–abs: max(var(–sgn-dir) – var(–sgn-ico), var(–sgn-ico) – var(–sgn-dir));
–hlg: calc(var(–sel)*(1 – min(1, var(–abs)))); /* spotlight present icon? Sure 1/ No 0 */
opacity: calc(1 – .85*(1 – var(–hlg)));
}

CodePen Embed Fallback

One other use case is making property values of things on a grid rely on the parity of the sum of horizontal –abs-i and vertical –abs-j distances from the center, –m. For instance, let’s say we do that for the background-color:

@property –floor {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

.cell {
–m: calc(.5*(var(–n) – 1));
–abs-i: max(var(–m) – var(–i), var(–i) – var(–m));
–abs-j: max(var(–m) – var(–j), var(–j) – var(–m));
–sum: calc(var(–abs-i) + var(–abs-j));
–floor: max(0, var(–sum)/2 – .5);
–mod: calc(var(–sum) – var(–floor)*2);
background: hsl(calc(90 + var(–mod)*180), 50%, 65%);
}

Background relying on parity of sum of horizontal and vertical distances to the center (stay demo)

We are able to spice issues up by utilizing the modulo 2 of the ground of the sum divided by 2:

@property –floor {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

@property –int {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

.cell {
–m: calc(.5*(var(–n) – 1));
–abs-i: max(var(–m) – var(–i), var(–i) – var(–m));
–abs-j: max(var(–m) – var(–j), var(–j) – var(–m));
–sum: calc(var(–abs-i) + var(–abs-j));
–floor: max(0, var(–sum)/2 – .5);
–int: max(0, var(–floor)/2 – .5);
–mod: calc(var(–floor) – var(–int)*2);
background: hsl(calc(90 + var(–mod)*180), 50%, 65%);
}

A extra fascinating variation of the earlier demo (stay demo)

We might additionally make each the course of a rotation and that of a conic-gradient() rely on the identical parity of the sum, –sum, of horizontal –abs-i and vertical –abs-j distances from the center, –m. That is achieved by horizontally flipping the factor if the sum, –sum, is even. Within the instance under, the rotation and measurement are additionally animated through Houdini (they each rely on a customized property, –f, which we register after which animate from 0 to 1), and so are the worm hue, –hue, and the conic-gradient() masks, each animations having a delay computed precisely as in earlier examples.

@property –floor {
syntax: ‘<integer>’;
initial-value: 0;
inherits: false;
}

.🐛 {
–m: calc(.5*(var(–n) – 1));
–abs-i: max(var(–m) – var(–i), var(–i) – var(–m));
–abs-j: max(var(–m) – var(–j), var(–j) – var(–m));
–sum: calc(var(–abs-i) + var(–abs-j));
–floor: calc(var(–sum)/2 – .5);
–mod: calc(var(–sum) – var(–floor)*2);
–sgn: calc(2*var(–mod) – 1); /* -1 if –mod is 0; 1 id –mod is 1 */
remodel:
scalex(var(–sgn))
scale(var(–f))
rotate(calc(var(–f)*180deg));
–hue: calc(var(–sgn)*var(–f)*360);
}

Grid wave: triangular rainbow worms (stay demo).

Lastly, one other massive use case for the methods defined thus far is shading not simply convex, but additionally concave animated 3D shapes utilizing completely no JavaScript! That is one subject that’s completely huge by itself and explaining all the pieces would take an article so long as this one, so I gained’t be going into it in any respect right here. However I’ve made a couple of movies the place I code a few such fundamental pure CSS 3D shapes (together with a picket star and a otherwise formed metallic one) from scratch and you may, after all, additionally take a look at the CSS for the next instance on CodePen.

Musical toy (stay demo)

The publish Utilizing Absolute Worth, Signal, Rounding and Modulo in CSS At this time appeared first on CSS-Tips. You’ll be able to help CSS-Tips by being an MVP Supporter.

    About Marketing Solution Australia

    We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.

    Request a free quote

    We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.

    Subscribe to our newsletter!

    More from our blog

    See all posts

    Leave a Comment