math – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Fri, 17 May 2024 19:25:50 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 math – CSS-Tricks https://css-tricks.com 32 32 45537868 Creating a Clock with the New CSS sin() and cos() Trigonometry Functions https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/ https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/#comments Wed, 08 Mar 2023 14:05:52 +0000 https://css-tricks.com/?p=377074 CSS trigonometry functions are here! Well, they are if you’re using the latest versions of Firefox and Safari, that is. Having this sort of mathematical power in CSS opens up a whole bunch of possibilities. In this tutorial, I thought …


Creating a Clock with the New CSS sin() and cos() Trigonometry Functions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS trigonometry functions are here! Well, they are if you’re using the latest versions of Firefox and Safari, that is. Having this sort of mathematical power in CSS opens up a whole bunch of possibilities. In this tutorial, I thought we’d dip our toes in the water to get a feel for a couple of the newer functions: sin() and cos().

There are other trigonometry functions in the pipeline — including tan() — so why focus just on sin() and cos()? They happen to be perfect for the idea I have in mind, which is to place text along the edge of a circle. That’s been covered here on CSS-Tricks when Chris shared an approach that uses a Sass mixin. That was six years ago, so let’s give it the bleeding edge treatment.

Here’s what I have in mind. Again, it’s only supported in Firefox and Safari at the moment:

So, it’s not exactly like words forming a circular shape, but we are placing text characters along the circle to form a clock face. Here’s some markup we can use to kick things off:

<div class="clock">
  <div class="clock-face">
    <time datetime="12:00">12</time>
    <time datetime="1:00">1</time>
    <time datetime="2:00">2</time>
    <time datetime="3:00">3</time>
    <time datetime="4:00">4</time>
    <time datetime="5:00">5</time>
    <time datetime="6:00">6</time>
    <time datetime="7:00">7</time>
    <time datetime="8:00">8</time>
    <time datetime="9:00">9</time>
    <time datetime="10:00">10</time>
    <time datetime="11:00">11</time>
  </div>
</div>

Next, here are some super basic styles for the .clock-face container. I decided to use the <time> tag with a datetime attribute. 

.clock {
  --_ow: clamp(5rem, 60vw, 40rem);
  --_w: 88cqi;
  aspect-ratio: 1;
  background-color: tomato;
  border-radius: 50%;
  container-type: inline;
  display: grid;
  height: var(--_ow);
  place-content: center;
  position: relative;
  width: var(--_ow);
}

I decorated things a bit in there, but only to get the basic shape and background color to help us see what we’re doing. Notice how we save the width value in a CSS variable. We’ll use that later. Not much to look at so far:

Large tomato colored circle with a vertical list of numbers 1-12 on the left.

It looks like some sort of modern art experiment, right? Let’s introduce a new variable, --_r, to store the circle’s radius, which is equal to half of the circle’s width. This way, if the width (--_w) changes, the radius value (--_r) will also update — thanks to another CSS math function, calc():

.clock {
  --_w: 300px;
  --_r: calc(var(--_w) / 2);
  /* rest of styles */
}

Now, a bit of math. A circle is 360 degrees. We have 12 labels on our clock, so want to place the numbers every 30 degrees (360 / 12). In math-land, a circle begins at 3 o’clock, so noon is actually minus 90 degrees from that, which is 270 degrees (360 - 90).

Let’s add another variable, --_d, that we can use to set a degree value for each number on the clock face. We’re going to increment the values by 30 degrees to complete our circle:

.clock time:nth-child(1) { --_d: 270deg; }
.clock time:nth-child(2) { --_d: 300deg; }
.clock time:nth-child(3) { --_d: 330deg; }
.clock time:nth-child(4) { --_d: 0deg; }
.clock time:nth-child(5) { --_d: 30deg; }
.clock time:nth-child(6) { --_d: 60deg; }
.clock time:nth-child(7) { --_d: 90deg; }
.clock time:nth-child(8) { --_d: 120deg; }
.clock time:nth-child(9) { --_d: 150deg; }
.clock time:nth-child(10) { --_d: 180deg; }
.clock time:nth-child(11) { --_d: 210deg; }
.clock time:nth-child(12) { --_d: 240deg; }

OK, now’s the time to get our hands dirty with the sin() and cos() functions! What we want to do is use them to get the X and Y coordinates for each number so we can place them properly around the clock face.

The formula for the X coordinate is radius + (radius * cos(degree)). Let’s plug that into our new --_x variable:

--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));

The formula for the Y coordinate is radius + (radius * sin(degree)). We have what we need to calculate that:

--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));

There are a few housekeeping things we need to do to set up the numbers, so let’s put some basic styling on them to make sure they are absolutely positioned and placed with our coordinates:

.clock-face time {
  --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
  --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
  --_sz: 12cqi;
  display: grid;
  height: var(--_sz);
  left: var(--_x);
  place-content: center;
  position: absolute;
  top: var(--_y);
  width: var(--_sz);
}

Notice --_sz, which we’ll use for the width and height of the numbers in a moment. Let’s see what we have so far.

Large tomato colored circle with off-centered hour number labels along its edge.

This definitely looks more like a clock! See how the top-left corner of each number is positioned at the correct place around the circle? We need to “shrink” the radius when calculating the positions for each number. We can deduct the size of a number (--_sz) from the size of the circle (--_w), before we calculate the radius:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Large tomato colored circle with hour number labels along its rounded edge.

Much better! Let’s change the colors, so it looks more elegant:

A white clock face with numbers against a dark gray background. The clock has no arms.

We could stop right here! We accomplished the goal of placing text around a circle, right? But what’s a clock without arms to show hours, minutes, and seconds?

Let’s use a single CSS animation for that. First, let’s add three more elements to our markup,

<div class="clock">
  <!-- after <time>-tags -->
  <span class="arm seconds"></span>
  <span class="arm minutes"></span>
  <span class="arm hours"></span>
  <span class="arm center"></span>
</div>

Then some common markup for all three arms. Again, most of this is just make sure the arms are absolutely positioned and placed accordingly:

.arm {
  background-color: var(--_abg);
  border-radius: calc(var(--_aw) * 2);
  display: block;
  height: var(--_ah);
  left: calc((var(--_w) - var(--_aw)) / 2);
  position: absolute;
  top: calc((var(--_w) / 2) - var(--_ah));
  transform: rotate(0deg);
  transform-origin: bottom;
  width: var(--_aw);
}

We’ll use the same animation for all three arms:

@keyframes turn {
  to {
    transform: rotate(1turn);
  }
}

The only difference is the time the individual arms take to make a full turn. For the hours arm, it takes 12 hours to make a full turn. The animation-duration property only accepts values in milliseconds and seconds. Let’s stick with seconds, which is 43,200 seconds (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

It takes 1 hour for the minutes arm to make a full turn. But we want this to be a multi-step animation so the movement between the arms is staggered rather than linear. We’ll need 60 steps, one for each minute:

animation: turn 3600s steps(60, end) infinite;

The seconds arm is almost the same as the minutes arm, but the duration is 60 seconds instead of 60 minutes:

animation: turn 60s steps(60, end) infinite;

Let’s update the properties we created in the common styles:

.seconds {
  --_abg: hsl(0, 5%, 40%);
  --_ah: 145px;
  --_aw: 2px;
  animation: turn 60s steps(60, end) infinite;
}
.minutes {
  --_abg: #333;
  --_ah: 145px;
  --_aw: 6px;
  animation: turn 3600s steps(60, end) infinite;
}
.hours {
  --_abg: #333;
  --_ah: 110px;
  --_aw: 6px;
  animation: turn 43200s linear infinite;
}

What if we want to start at the current time? We need a little bit of JavaScript:

const time = new Date();
const hour = -3600 * (time.getHours() % 12);
const mins = -60 * time.getMinutes();
app.style.setProperty('--_dm', `${mins}s`);
app.style.setProperty('--_dh', `${(hour+mins)}s`);

I’ve added id="app" to the clockface and set two new custom properties on it that set a negative animation-delay, as Mate Marschalko did when he shared a CSS-only clock. The getHours() method of JavaScipt’s Date object is using the 24-hour format, so we use the remainder operator to convert it into 12-hour format.

In the CSS, we need to add the animation-delay as well:

.minutes {
  animation-delay: var(--_dm, 0s);
  /* other styles */
}

.hours {
  animation-delay: var(--_dh, 0s);
  /* other styles */
}

Just one more thing. Using CSS @supports and the properties we’ve already created, we can provide a fallback to browsers that do not supprt sin() and cos(). (Thank you, Temani Afif!):

@supports not (left: calc(1px * cos(45deg))) {
  time {
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
  }
}

And, voilà! Our clock is done! Here’s the final demo one more time. Again, it’s only supported in Firefox and Safari at the moment.

What else can we do?

Just messing around here, but we can quickly turn our clock into a circular image gallery by replacing the <time> tags with <img> then updating the width (--_w) and radius (--_r) values:

Let’s try one more. I mentioned earlier how the clock looked kind of like a modern art experiment. We can lean into that and re-create a pattern I saw on a poster (that I unfortunately didn’t buy) in an art gallery the other day. As I recall, it was called “Moon” and consisted of a bunch of dots forming a circle.

A large circle formed out of a bunch of smaller filled circles of various earthtone colors.

We’ll use an unordered list this time since the circles don’t follow a particular order. We’re not even going to put all the list items in the markup. Instead, let’s inject them with JavaScript and add a few controls we can use to manipulate the final result.

The controls are range inputs (<input type="range">) which we’ll wrap in a <form> and listen for the input event.

<form id="controls">
  <fieldset>
    <label>Number of rings
      <input type="range" min="2" max="12" value="10" id="rings" />
    </label>
    <label>Dots per ring
      <input type="range" min="5" max="12" value="7" id="dots" />
    </label>
    <label>Spread
      <input type="range" min="10" max="40" value="40" id="spread" />
    </label>
  </fieldset>
</form>

We’ll run this method on “input”, which will create a bunch of <li> elements with the degree (--_d) variable we used earlier applied to each one. We can also repurpose our radius variable (--_r) .

I also want the dots to be different colors. So, let’s randomize (well, not completely randomized) the HSL color value for each list item and store it as a new CSS variable, --_bgc:

const update = () => {
  let s = "";
  for (let i = 1; i <= rings.valueAsNumber; i++) {
    const r = spread.valueAsNumber * i;
    const theta = coords(dots.valueAsNumber * i);
    for (let j = 0; j < theta.length; j++) {
      s += `<li style="--_d:${theta[j]};--_r:${r}px;--_bgc:hsl(${random(
        50,
        25
      )},${random(90, 50)}%,${random(90, 60)}%)"></li>`;
    }
  }
  app.innerHTML = s;
}

The random() method picks a value within a defined range of numbers:

const random = (max, min = 0, f = true) => f ? Math.floor(Math.random() * (max - min) + min) : Math.random() * max;

And that’s it. We use JavaScript to render the markup, but as soon as it’s rendered, we don’t really need it. The sin() and cos() functions help us position all the dots in the right spots.

Final thoughts

Placing things around a circle is a pretty basic example to demonstrate the powers of trigonometry functions like sin() and cos(). But it’s really cool that we are getting modern CSS features that provide new solutions for old workarounds I’m sure we’ll see way more interesting, complex, and creative use cases, especially as browser support comes to Chrome and Edge.


Creating a Clock with the New CSS sin() and cos() Trigonometry Functions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/creating-a-clock-with-the-new-css-sin-and-cos-trigonometry-functions/feed/ 1 377074
Using Absolute Value, Sign, Rounding and Modulo in CSS Today https://css-tricks.com/using-absolute-value-sign-rounding-and-modulo-in-css-today/ https://css-tricks.com/using-absolute-value-sign-rounding-and-modulo-in-css-today/#comments Wed, 28 Jul 2021 14:37:34 +0000 https://css-tricks.com/?p=320544 For quite a while now, the CSS spec has included a lot of really useful mathematical functions, such as trigonometric functions (sin(), cos(), tan(), asin(), acos(), atan(), atan2()), exponential functions (…


Using Absolute Value, Sign, Rounding and Modulo in CSS Today originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
For quite a while now, the CSS spec has included a lot of really useful mathematical functions, such as trigonometric functions (sin(), cos(), tan(), asin(), acos(), atan(), atan2()), exponential functions (pow(), exp(), sqrt(), log(), hypot()), sign-related functions (abs(), sign()) and stepped value functions (round(), mod(), rem()).

However, these are not yet implemented in any browser, so this article is going to show how, using CSS features we already have, we can compute the values that abs(), sign(), round() and mod() should return. And then we’ll see what cool things this allows us to build today.

Screenshot collage - a 2x2 grid. The first one shows the items of a full-screen navigation sliding down with a delay that's proportional to the distance to the selected one. The second one shows a cube with each face made of neon tiles; these tiles shrink and go inwards, into the cube, with a delay that depends on the distance from the midlines of the top face. The third one is a time progress with a tooltip showing the elapsed time in a mm::ss format. The fourth one is a 3D rotating musical toy with wooden and metallic stars and a wooden crescent moon hanging from the top.
A few of the things these functions allow us to make.

Note that none of these techniques were ever meant to work in browsers from back in the days when dinosaurs roamed the internet. Some of them even depend on the browser supporting the ability to register custom properties (using @property), which means they’re limited to Chromium for now.

The computed equivalents

--abs

We can get this by using the new CSS max() function, which is already implemented in the current versions of all major browsers.

Let’s say we have a custom property, --a. We don’t know whether this is positive or negative and we want to get its absolute value. We do this by picking the maximum between this value and its additive inverse:

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

If --a is positive, this means it’s greater than zero, and multiplying it with -1 gives us a negative number, which is always smaller than zero. That, in turn, is always smaller than the positive --a, so the result returned by max() is equal to var(--a).

If --a is negative, this means it’s smaller than zero, and that multiplying it by -1 gives us a positive number, which is always bigger than zero, which, in turn, is always bigger than the negative --a. So, the result returned by max() is equal to -1*var(--a).

--sign

This is something we can get using the previous section as the sign of a number is that number divided by its absolute value:

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

A very important thing to note here is that this only works if --a is unitless, as we cannot divide by a number with a unit inside calc().

Also, if --a is 0, this solution works only if we register --sign (this is only supported in Chromium browsers at this point) with an initial-value of 0:

@property --sign {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false /* or true depending on context */
}

This is because --a, being 0, also makes --abs compute to 0 — and dividing by 0 is invalid in CSS calc() — so we need to make sure --sign gets reset to 0 in this situation. Keep in mind that this does not happen if we simply set it to 0 in the CSS prior to setting it to the calc() value and we don’t register it:

--abs: max(var(--a), -1*var(--a));
--sign: 0; /* doesn't help */
--sign: calc(var(--a)/var(--abs));

In practice, I’ve also often used the following version for integers:

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

Here, we’re using a clamp() function. This takes three arguments: a minimum allowed value -1, a preferred value var(--a) and a maximum allowed value, 1. The value returned is the preferred value as long as it’s between the lower and upper bounds and the limit that gets exceeded otherwise.

If --a is a negative integer, this means it’s smaller or equal to -1, the lower bound (or the minimum allowed value) of our clamp() function, so the value returned is -1. If it’s a positive integer, this means it’s greater or equal to 1, the upper bound (or the maximum allowed value) of the clamp() function, so the value returned is 1. And finally, if --a is 0, it’s between the lower and upper limits, so the function returns its value (0 in this case).

This method has the advantage of being simpler without requiring Houdini support. That said, note that it only works for unitless values (comparing a length or an angle value with integers like ±1 is like comparing apples and oranges — it doesn’t work!) that are either exactly 0 or at least as big as 1 in absolute value. For a subunitary value, like -.05, our method above fails, as the value returned is -.05, not -1!

My first thought was that we can extend this technique to subunitary values by introducing a limit value that’s smaller than the smallest non-zero value we know --a can possibly take. For example, let’s say our limit is .000001 — this would allow us to correctly get -1 as the sign for -.05, and 1 as the sign for .0001!

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

Temani Afif suggested a simpler version that would multiply --a by a very large number in order to produce a superunitary value.

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

I eventually settled on dividing --a by the limit value because it just feels a bit more intuitive to see what minimum non-zero value it won’t go below.

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

--round (as well as --ceil and --floor)

This is one I was stuck on for a while until I got a clever suggestion for a similar problem from Christian Schaefer. Just like the case of the sign, this only works on unitless values and requires registering the --round variable as an <integer> so that we force rounding on whatever value we set it to:

@property --round {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false /* or true depending on context */
}

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

By extension, we can get --floor and --ceil if we subtract or add .5:

@property --floor {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false /* or true depending on context */
}

@property --ceil {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false /* or true depending on context */
}

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

--mod

This builds on the --floor technique in order to get an integer quotient, which then allows us to get the modulo value. This means that both our values must be unitless.

@property --floor {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false /* or true depending on context */
}

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

Use cases

What sort of things can we do with the technique? Let’s take a good look at three use cases.

Effortless symmetry in staggered animations (and not only!)

While the absolute value can help us get symmetrical results for a lot of properties, animation-delay and transition-delay are the ones where I’ve been using it the most, so let’s see some examples of that!

We put --n items within a container, each of these items having an index --i. Both --n and --i are variables we pass to the CSS via style attributes.

- let n = 16;

.wrap(style=`--n: ${n}`)
  - for(let i = 0; i < n; i++)
    .item(style=`--i: ${i}`)

This gives us the following compiled HTML:

<div class='wrap' style='--n: 16'>
  <div class='item' style='--i: 0'></div>
  <div class='item' style='--i: 1'></div>
  <!-- more such items -->
</div>

We set a few styles such that the items are laid out in a row and are square with a non-zero edge length:

$r: 2.5vw;

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

.item { padding: $r; }
Screenshot showing the items lined in a row and DevTools with the HTML structure and the styles applied.
The result so far.

Now we add two sets of keyframes to animate a scaling transform and a box-shadow. The first set of keyframes, grow, makes our items scale up from nothing at 0% to full size at 50%, after which they stay at their full size until the end. The second set of keyframes, melt, shows us the items having inset box shadows that cover them fully up to the midway point in the animation (at 50%). That’s also when the items reach full size after growing from nothing. Then the spread radius of these inset shadows shrinks until it gets down to nothing at 100%.

$r: 2.5vw;

.item {
  padding: $r;
  animation: a $t infinite;
  animation-name: grow, melt;
}

@keyframes grow {
  0% { transform: scale(0); }
  50%, 100% { transform: none; }
}

@keyframes melt {
  0%, 50% { box-shadow: inset 0 0 0 $r; }
  100% { box-shadow: inset 0 0; }
}
Animated gif. Shows 16 black square tiles in a row growing from nothing to full size, then melting from the inside until they disappear. The cycle then repeats. In this case, all tiles animate at the same time.
The base animation (live demo).

Now comes the interesting part! We compute the middle between the index of the first item and that of the last one. This is the arithmetic mean of the two (since our indices are zero-based, the first and last are 0 and n - 1 respectively):

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

We get the absolute value, --abs, of the difference between this middle, --m, and the item 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: grow, melt;

The absolute value ,--abs, of the difference between the middle, --m, and the item index, --i, can be as small as 0 (for the middle item, if --n is odd) and as big as --m (for the end items). This means dividing it by --m always gives us a value in the [0, 1] interval, which we then multiply with the animation duration $t to ensure every item has a delay between 0s and the animation-duration.

Note that we’ve also set animation-fill-mode to backwards. Since most items will start the animations later, this tells the browser to keep them with the styles in the 0% keyframes until then.

In this particular case, we wouldn’t see any difference without it either because, while the items would be at full size (not scaled to nothing like in the 0% keyframe of the grow animation), they would also have no box-shadow until they start animating. However, in a lot of other cases, it does make a difference and we shouldn’t forget about it.

Another possibility (one that doesn’t involve setting the animation-fill-mode) would be to ensure the animation-delay is always 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: grow, melt;

Both options are valid, and which one you use depends on what you prefer to happen at the very beginning. I generally tend to go for negative delays because they make more sense when recording the looping animation to make a gif like the one below, which illustrates how the animation-delay values are symmetrical with respect to the middle.

Animated gif. Shows 16 black square tiles in a row, each of them growing from nothing to full size, then melting from the inside until they disappear, with the cycle then repeating. Only now, they don't all animate at the same time. The closer they are to the middle, the sooner they start their animation, those at the very ends of the row being one full cycle behind those in the very middle.
The staggered looping animation.

For a visual comparison between the two options, you can rerun the following demo to see what happens at the very beginning.

A fancier example would be the following:

Navigation links sliding up and then back down with a delay proportional to how far they are from the selected one.

Here, each and every one of the --n navigation links and corresponding recipe articles have an index --idx. Whenever a navigation link is hovered or focused, its --idx value is read and set to the current index, --k, on the body. If none of these items is hovered or focused, --k gets set to a value outside 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.

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 means the sum of these two ratios is always in the [0, 2] interval. If we want to reduce it to the [0, 1] interval, we need to divide it by 2 (or multiply by .5, same thing).

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

This gives us delays that are in the [0s, $t] interval. We can take the denominator, var(--m), out of the parenthesis to simplify the above formula a bit:

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

Just like the previous case, this makes grid items start animating later the further they are from the middle of the grid. We should use animation-fill-mode: backwards to ensure they stay in the state specified by the 0% keyframes until the delay time has elapsed and they start animating.

Alternatively, we can subtract one animation duration $t from all delays to make sure all grid items have already started their animation when the page loads.

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

This gives us the following result:

Animated gif. Shows an 8x8 grid of tiles, each of them growing from nothing to full size, then melting from the inside until they disappear, with the cycle then repeating. The smaller the sum of their distances to the middle is, the sooner they start their animation, those at the very corners of the grid being one full cycle behind those in the very middle.
The staggered 2D animation (live demo).

Let’s now see a few more interesting examples. We won’t be going into details about the “how” behind them as the symmetrical value technique works exactly the same as for the previous ones and the rest is outside the scope of this article. However, there is a link to a CodePen demo in the caption for each of the examples below, and most of these Pens also come with a recording that shows me coding them from scratch.

In the first example, each grid item is made up of two triangles that shrink down to nothing at opposite ends of the diagonal they meet along and then grow back to full size. Since this is an alternating animation, we let the delays to stretch across two iterations (a normal one and a reversed one), which means we don’t divide the sum of ratios in half anymore and we subtract 2 to ensure every item has a negative 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 (live demo)

In the second example, each grid item has a gradient at an angle that animates from 0deg to 1turn. This is possible via Houdini as explained in this article about the state of animating gradients with CSS.

Field wave: cell gradient rotation (live demo)

The third example is very similar, except the animated angle is used by a conic-gradient instead of a linear one and also by the hue of the first stop.

Rainbow hour wave (live demo)

In the fourth example, each grid cell contains seven rainbow dots that oscillate up and down. The oscillation delay has a component that depends on the cell indices in the exact same manner as the previous grids (the only thing that’s different here is the number of columns differs from the number of rows, so we need to compute two middle indices, one along each of the two dimensions) and a component that depends on the dot index, --idx, relative to the number 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 (live demo)

In the fifth example, the tiles making up the cube faces shrink and move inwards. The animation-delay for the top face is computed exactly as in our first 2D demo.

Breathe into me: neon waterfall (live demo and a previous iteration)

In the sixth example, we have a grid of columns oscillating up and down.

Column wave (live demo)

The animation-delay isn’t the only property we can set to have symmetrical values. We can also do this with the items’ dimensions. In the seventh example below, the tiles are distributed around half a dozen rings starting from the vertical (y) axis and are scaled using a factor that depends on how far they are from the top point of the rings. This is basically the 1D case with the axis curved on a circle.

Circular grid melt (live demo)

The eighth example shows ten arms of baubles that wrap around a big sphere. The size of these baubles depends on how far they are from the poles, the closest ones being the smallest. This is done by computing the middle index, --m, for the dots on an arm and the absolute value, --abs, of the difference between it and the current bauble index, --j, then using the ratio between this absolute value and the middle index to get the sizing factor, --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});
Travel inside the sphere (live demo)

Different styles for items before and after a certain (selected or middle) one

Let’s say we have a bunch of radio buttons and labels, with the labels having an index set as a custom property, --i. We want the labels before the selected item to have a green background, the label of the selected item to have a blue background and the rest of the labels to be grey. On the body, we set the index of the currently selected option as another custom property, --k.

- let n = 8;
- let k = Math.round((n - 1)*Math.random());

body(style=`--k: ${k}`)
  - for(let i = 0; i < n; i++)
    - let id = `r${i}`;
    input(type='radio' name='r' id=id checked=i===k)
    label(for=id style=`--i: ${i}`) Option ##{i}

This compiles to the following HTML:

<body style='--k: 1'>
  <input type='radio' name='r' id='r0'/>
  <label for='r0' style='--i: 0'>Option #0</label>
  <input type='radio' name='r' id='r1' checked='checked'/>
  <label for='r1' style='--i: 1'>Option #1</label>
  <input type='radio' name='r' id='r2'/>
  <label for='r2' style='--i: 2'>Option #2</label>
  <!-- more options -->
</body>

We set a few layout and prettifying styles, including a gradient background on the labels that creates three vertical stripes, each occupying a third of the background-size (which, for now, is just the default 100%, the full element width):

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

body {
  display: grid;
  grid-gap: .25em 0;
  grid-template-columns: repeat(2, max-content);
  align-items: center;
  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);
  color: #fff;
  cursor: pointer;
}
Screenshot showing radio inputs and their labels on two grid columns. The labels have a vertical three stripe background with the first stripe being green, the second one blue and the last one grey.
The result so far.

From the JavaScript, we update the value of --k whenever we select a different option:

addEventListener('change', e => {
  let _t = e.target;
	
  document.body.style.setProperty('--k', +_t.id.replace('r', ''))
})

Now comes the interesting part! For our label elements, we compute the sign, --sgn, of the difference between the label index, --i, and the index of the currently selected option, --k. We then use this --sgn value to compute the background-position when the background-size is set to 300% — that is, three times the label’s width because we may have of three possible backgrounds: one for the case when the label is for an option before the selected one, a second for the case when the label is for the selected option, and a third for the case when the label is for an option after the selected 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 option before the selected one), then --sgn is -1 and the background-position computes to 50%*(1 + -1) = 50%*0 = 0%, meaning we only see the first vertical stripe (the green one).

If --i is equal --k (the case of the label for the selected option), then --sgn is 0 and the background-position computes to 50%*(1 + 0) = 50%*1 = 50%, so we only see the vertical stripe in the middle (the blue one).

If --i is greater than --k (the case of a label for an option after the selected one), then --sgn is 1 and the background-position computes to 50%*(1 + 1) = 50%*2 = 100%, meaning we only see the last vertical stripe (the grey one).

A more aesthetically appealing example would be the following navigation where the vertical bar is on the side closest to the selected option and, for the selected one, it spreads across the entire element.

This uses a structure that’s similar to that of the previous demo, with radio inputs and labels for the navigation items. The moving “background” is actually an ::after pseudo-element whose translation value depends on the sign, --sgn. The text is a ::before pseudo-element whose position is supposed to be in the middle of the white area, so its translation value also depends on --sgn.

/* relevant styles */
label {
  --sgn: clamp(-1, var(--k) - var(--i), 1);
  
  &::before {
    transform: translate(calc(var(--sgn)*-.5*#{$pad}))
  }
  &::after {
    transform: translate(calc(var(--sgn)*(100% - #{$pad})))
  }
}

Let’s now quickly look at a few more demos where computing the sign (and maybe the absolute value as well) comes in handy.

First up, we have a square grid of cells with a radial-gradient whose radius shrinks from covering the entire cell to nothing. This animation has a delay computed as explained in the previous section. What’s new here is that the coordinates of the radial-gradient circle depend on where the cell is positioned with respect to the middle of the grid — that is, on the signs of the differences between the column --i and row --j indices and the middle index, --m.

/* relevant 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), transparent 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 (live demo)

Then we have a double spiral of tiny spheres where both the sphere diameter --d and the radial distance --x that contributes to determining the sphere position depend on the absolute value --abs of the difference between each one’s index, --i, and the middle index, --m. The sign, --sgn, of this difference is used to determine the spiral rotation direction. This depends on where each sphere is with respect to the middle – that is, whether its index ,--i, is smaller or bigger than the middle index, --m.

/* relevant styles */
--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 determine sphere position */
--x: calc(var(--abs)*2*#{$d}/var(--n-dot)); /* how far from spiral axis */
--z: calc((var(--i) - var(--m))*2*#{$d}/var(--n-dot)); /* position with respect to screen plane */
width: var(--d); height: var(--d);
transform: 
  /* change rotation direction by changing x axis direction */
  scalex(var(--sgn)) 
  rotate(var(--a)) 
  translate3d(var(--x), 0, var(--z)) 
  /* reverse rotation so the sphere is always seen from the front */
  rotate(calc(-1*var(--a))); 
  /* reverse scaling so lighting on sphere looks consistent */
  scalex(var(--sgn))
No perspective (live demo)

Finally, we have a grid of non-square boxes with a border. These boxes have a mask created using a conic-gradient with an animated start angle, --ang. Whether these boxes are flipped horizontally or vertically depends on where they are with respect to the middle – that is, on the signs of the differences between the column --i and row --j indices and the middle index, --m. The animation-delay depends on the absolute values of these differences and is computed as explained in the previous section. We also have a gooey filter for a nicer “wormy” look, but we won’t be going into that here.

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

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

.box {
  --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);
  transform: scale(var(--sgn-i), var(--sgn-j));
  mask:
    repeating-conic-gradient(from var(--ang, 0deg), 
        red 0% 12.5%, transparent 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 (live demo)

Time (and not only) formatting

Let’s say we have an element for which we store a number of seconds in a custom property, --val, and we want to display this in a mm:ss format, for example.

We use the floor of the ratio between --val and 60 (the number of seconds in a minute) to get the number of minutes and modulo for the number of seconds past that number of minutes. Then we use a clever little counter trick to display 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: 
      counter(min, decimal-leading-zero) ':' 
      counter(sec, decimal-leading-zero);
  }
}

This works in most situations, but we encounter a problem when --val is exactly 0. In this case, 0/60 is 0 and then subtracting .5, we get -.5, which gets rounded to what’s the bigger adjacent integer in absolute value. That is, -1, not 0! This means our result will end up being -01:60, not 00:00!

Fortunately, we have a simple fix and that’s to slightly alter the formula for getting the number of minutes, --min:

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

There are other formatting options too, as illustrated below:

/* shows time formatted as 2:09 */
content: counter(min) ':' counter(sec, decimal-leading-zero);

/* shows time formatted as 2m9s */
content: counter(min) 'm' counter(sec) 's';

We can also apply the same technique to format the time as hh:mm:ss (live test).

@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: 
      counter(hrs, decimal-leading-zero) ':' 
      counter(min, decimal-leading-zero) ':' 
      counter(sec, decimal-leading-zero);
  }
}

This is a technique I’ve used for styling the output of native range sliders such as the one below.

Screenshot showing a styled slider with a tooltip above the thumb indicating the elapsed time formatted as mm:ss. On the right of the slider, there's the remaining time formatted as -mm:ss.
Styled range input indicating time (live demo)

Time isn’t the only thing we can use this for. Counter values have to be integer values, which means the modulo trick also comes in handy for displaying decimals, as in the second slider seen below.

Screenshot showing three styled sliders withe second one having a tooltip above the thumb indicating the decimal value.
Styled range inputs, one of which has a decimal output (live demo)

A couple more such examples:

Screenshot showing multiple styled sliders with the third one being focused and showing a tooltip above the thumb indicating the decimal value.
Styled range inputs, one of which has a decimal output (live demo)
Screenshot showing two styled sliders with the second one being focused and showing a tooltip above the thumb indicating the decimal value.
Styled range inputs, one of which has a decimal output (live demo)

Even more use cases

Let’s say we have a volume slider with an icon at each end. Depending on the direction we move the slider’s thumb in, one of the two icons gets highlighted. This is possible by getting the absolute value, --abs, of the difference between each icon’s sign, --sgn-ico (-1 for the one before the slider, and 1 for the one after the slider), and the sign of the difference, --sgn-dir, between the slider’s current value, --val, and its previous value, --prv. If this is 0, then we’re moving in the direction of the current icon so we set its opacity to 1. Otherwise, we’re moving away from the current icon, so we keep its opacity at .15.

This means that, whenever the range input’s value changes, not only do we need to update its current value, --val, on its parent, but we need to update its previous value, which is another custom property, --prv, on the same parent wrapper:

addEventListener('input', e => {
  let _t = e.target, _p = _t.parentNode;
	
  _p.style.setProperty('--prv', +_p.style.getPropertyValue('--val'))
  _p.style.setProperty('--val', +_t.value)
})

The sign of their difference is the sign of the direction, --sgn-dir, we’re going in and the current icon is highlighted if its sign, --sgn-ico, and the sign of the direction we’re going in, --sgn-dir, coincide. That is, if the absolute value, --abs, of their difference is 0 and, at the same time, the parent wrapper is selected (it’s either being hovered or the range input in it has focus).

[role='group'] {
  --dir: calc(var(--val) - var(--prv));
  --sgn-dir: clamp(-1, var(--dir), 1);
  --sel: 0; /* is the slider focused or hovered? Yes 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)))); /* highlight current icon? Yes 1/ No 0 */
  opacity: calc(1 - .85*(1 - var(--hlg)));
}

Another use case is making property values of items on a grid depend on the parity of the sum of horizontal --abs-i and vertical --abs-j distances from the middle, --m. For example, let’s say we do this 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%);
}
Screenshot showing a 16x16 grid where each tile is either lime or purple.
Background depending on parity of sum of horizontal and vertical distances to the middle (live demo)

We can spice things up by using the modulo 2 of the floor 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%);
}
Screenshot showing a 16x16 grid where each tile is either lime or purple.
A more interesting variation of the previous demo (live demo)

We could also make both the direction of a rotation and that of a conic-gradient() depend on the same parity of the sum, --sum, of horizontal --abs-i and vertical --abs-j distances from the middle, --m. This is achieved by horizontally flipping the element if the sum, --sum, is even. In the example below, the rotation and size are also animated via Houdini (they both depend on a custom property, --f, which we register and then animate from 0 to 1), and so are the worm hue, --hue, and the conic-gradient() mask, both animations having a delay computed exactly as in previous 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 */
  transform: 
    scalex(var(--sgn)) 
    scale(var(--f)) 
    rotate(calc(var(--f)*180deg));
  --hue: calc(var(--sgn)*var(--f)*360);
}
Grid wave: triangular rainbow worms (live demo).

Finally, another big use case for the techniques explained so far is shading not just convex, but also concave animated 3D shapes using absolutely no JavaScript! This is one topic that’s absolutely massive on its own and explaining everything would take an article as long as this one, so I won’t be going into it at all here. But I have made a few videos where I code a couple of such basic pure CSS 3D shapes (including a wooden star and a differently shaped metallic one) from scratch and you can, of course, also check out the CSS for the following example on CodePen.

Musical toy (live demo)


Using Absolute Value, Sign, Rounding and Modulo in CSS Today originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-absolute-value-sign-rounding-and-modulo-in-css-today/feed/ 6 320544
Lots of Ways to Use Math.random() in JavaScript https://css-tricks.com/lots-of-ways-to-use-math-random-in-javascript/ https://css-tricks.com/lots-of-ways-to-use-math-random-in-javascript/#comments Mon, 30 Nov 2020 15:49:48 +0000 https://css-tricks.com/?p=326055 Math.random() is an API in JavaScript. It is a function that gives you a random number. The number returned will be between 0 (inclusive, as in, it’s possible for an actual 0 to be returned) and 1 (exclusive, as in, …


Lots of Ways to Use Math.random() in JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Math.random() is an API in JavaScript. It is a function that gives you a random number. The number returned will be between 0 (inclusive, as in, it’s possible for an actual 0 to be returned) and 1 (exclusive, as in, it’s not possible for an actual 1 to be returned).

Math.random(); // returns a random number lower than 1

This is incredibly useful for gaming, animations, randomized data, generative art, random text generation, and more! It can be used for web development, mobile applications, computer programs, and video games.

Whenever we need randomization in our work, we can use this function! Let’s look at eight different ways we can use it. These examples are all from different authors doing something interesting with this API.

Animation

To spawn an object and animate it, we use Math.random. The neon lines form spontaneous hexagons but randomization is also in its generative sparks. 

Computer-generated music

This program takes the traditional melody of “Auld Lang Syne” and plays random notes from it in piano. A change package is created from the count data and a random number is generated to select a value. The octave is also randomly selected.

Display a random image

Images are stored in an array. A number is generated and multiplied by the number of images in the array via array.length. Then Math.floor rounds the value to a round number and sets the image src in the HTML when the page is loaded or the button is clicked.

Random background color

This is where the magic happens:

const random = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

The first line of code randomly shuffles the array and the second line returns a random umber between 0 and 10. In the example of a random color background, the range of colors and specifics such as hues, saturations, and shades can be set. 

For another method for generating a random hex color, check out this article by Chris Coyer. 

Generative art

In this morphing fractal curve, Math.random is used twice to set the colors for the gradient and once more for the max radius of the curves. This is a great way to construct an entirely new appearance with every iteration!

Word generator

We replace the header with a randomly selected word from an array using Math.random:

var word = words[Math.floor(Math.random() * words.length)] + "!";

This is a lot like the random image example — the perfect sort of practice for beginners! 

API key generator

Here’s a super real-world practical use case for random numbers! The demo generates 16 random numbers to create a universally unique identifier (UUID) that can be used as a key that provides access to an API.

Text scramble

A few phrases are stored and displayed in sequence, separated by an animation that appears to scramble the letters with random characters between phrases that are selected by Math.random.

Rock Paper Scissors

In this childhood classic game of Rock Paper Scissors, Math.random is used to generate a randomized move for the computer playing as the opponent. It makes a pick from the three available moves.

Strong Password Generator

This password generator uses Math.random to get a password array filled with uppercase and lowercase letters then adds random digits to the generated password. This is another great practical example!

A couple of notes…

It’s possible you have questions after seeing Math.random in these examples. There are a couple I see come up often…

Is Math.random() really random?

Not exactly. Math.random() returns a pseudo-random number. This algorithm is called a pseudo-random number generator (or PRNG). This means its randomization can be reproduced under certain circumstances. 

The randomization is based on the algorithm xorshift128+, which is likely running on your browser.

So, it’s random-ish.

How do you handle repeated values?

There are many methods to achieve unique values without repetition. The Fisher-Yates is one great way to prevent getting the same number twice by shuffling the sequence. Math.random will select a value from the shuffled array of a finite sequence demonstrated by the code snippet below.

function shuffle (array) {
  var i = 0
    , j = 0
    , temp = null

  for (i = array.length - 1; i > 0; i -= 1) {
    j = Math.floor(Math.random() * (i + 1))
    temp = array[i]
    array[i] = array[j]
    array[j] = temp
  }
}

Is Math.random() the same as WebCrypto?

As you’ve seen from this article, Math.random() is awesome! However, if you dealing with sensitive applications and need a more secure method of randomization, I’d recommend WebCrypto. Reasons you may want to use WebCrypto include temporary verification codes, random password generation, randomized lottery numbers, etc. 

If you need randomization for the purposes of cybersecurity, cryptography, or statistics ,  use the function window.crypto.getRandomValues and check out Mozilla’s documentation on the WebCrypto API.


Lots of Ways to Use Math.random() in JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/lots-of-ways-to-use-math-random-in-javascript/feed/ 12 326055
A Complete Guide to calc() in CSS https://css-tricks.com/a-complete-guide-to-calc-in-css/ https://css-tricks.com/a-complete-guide-to-calc-in-css/#comments Tue, 17 Mar 2020 21:05:02 +0000 https://css-tricks.com/?p=303923 CSS has a special calc() function for doing basic math. In this guide, let’s cover just about everything there is to know about this very useful function.

Here’s an example:

.main-content {
  /* Subtract 80px from 100vh */
  height: 


A Complete Guide to calc() in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS has a special calc() function for doing basic math. In this guide, let’s cover just about everything there is to know about this very useful function.

Here’s an example:

.main-content {
  /* Subtract 80px from 100vh */
  height: calc(100vh - 80px);
}

In this guide, let’s cover just about everything there is to know about this very useful function.

calc() is for values

The only place you can use the calc() function is in values. See these examples where we’re setting the value for a number of different properties.

.el {
  font-size: calc(3vw + 2px);
  width:     calc(100% - 20px);
  height:    calc(100vh - 20px);
  padding:   calc(1vw + 5px);
}

It could be used for only part of a property too, for example:

.el {
  margin: 10px calc(2vw + 5px);
  border-radius: 15px calc(15px / 3) 4px 2px;
  transition: transform calc(1s - 120ms);
}

It can even be a part of another function that forms a part of a property! For example, here’s calc() used within the color stops of a gradient

.el {
  background: #1E88E5 linear-gradient(
    to bottom,
    #1E88E5,
    #1E88E5 calc(50% - 10px),
    #3949AB calc(50% + 10px),
    #3949AB
  );
}

calc() is for lengths and other numeric things

Notice all the examples above are essentially numbers-based. We’ll get to some of the caveats of how the numbers can be used (because sometimes you don’t need a unit), but this is for number math, not strings or anything like that.

.el {
  /* Nope! */
  counter-reset: calc("My " + "counter");
}
.el::before {
  /* Nope! */
  content: calc("Candyman " * 3);
}

There are many lengths of CSS though, and they can all be used with calc():

  • px
  • %
  • em
  • rem
  • in
  • mm
  • cm
  • pt
  • pc
  • ex
  • ch
  • vh
  • vw
  • vmin
  • vmax

Unit-less numbers are acceptable, too. For example line-height: calc(1.2 * 1.2); as well as angles like transform: rotate(calc(10deg * 5));.

You can also not perform any calculation and it is still valid:

.el {
  /* Little weird but OK */
  width: calc(20px);
}

Nope on media queries

When calc() is used correctly (length units used as a value to a property), sadly, calc() won’t work when applied to media queries.

@media (max-width: 40rem) {
  /* Narrower or exactly 40rem */
}

/* Nope! */
@media (min-width: calc(40rem + 1px)) {
  /* Wider than 40rem */
}

It would be cool someday because you could do mutually exclusive media queries in a fairly logical way (like above).

Mixing units 🎉

This is perhaps the most valuable feature of calc()! Almost every example above has already done this, but just to put a point on it, here it is mixing different units:

/* Percentage units being mixed with pixel units */
width: calc(100% - 20px);

That’s saying: As wide as the element is, minus 20 pixels.

There is literally no way to pre-calculate that value in pixels alone in a fluid width situation. In other words, you can’t preprocess calc() with something like Sass as an attempt to complete a polyfill. Not that you need to, as the browser support is fine. But the point is that it has to be done in the browser (at “runtime”) when you mix units in this way, which is most of the value of calc().

Here’s some other examples of mixing units:

transform: rotate(calc(1turn + 45deg));

animation-delay: calc(1s + 15ms);

Those probably could be preprocessed as they mix units that aren’t relative to anything that is determined at runtime.

Comparison to preprocessor math

We just covered that you can’t preprocess the most useful things that calc() can do. But there is a smidge of overlap. For example, Sass has math built into it, so you can do things like:

$padding: 1rem;

.el[data-padding="extra"] {
  padding: $padding + 2rem; // processes to 3rem;
  margin-bottom: $padding * 2; // processes to 2rem; 
}

Even math with units is working there, adding same-unit values together or multiplying by unit-less numbers. But you can’t mix units and it has similar limitations to calc() (e.g. like multiplying and dividing must be with unit-less numbers).

Show the math

Even you aren’t using a feature that is uniquely possible only with calc(), it can be used to “show your work” inside CSS. For example, say you need to calculate exactly 17th the width of an element…

.el {
  /* This is easier to understand */
  width: calc(100% / 7);

  /* Than this is */
  width: 14.2857142857%;
}

That might pan out in some kind of self-created CSS API like:

[data-columns="7"] .col { width: calc(100% / 7); }
[data-columns="6"] .col { width: calc(100% / 6); }
[data-columns="5"] .col { width: calc(100% / 5); }
[data-columns="4"] .col { width: calc(100% / 4); }
[data-columns="3"] .col { width: calc(100% / 3); }
[data-columns="2"] .col { width: calc(100% / 2); }

The Math operators of calc()

You’ve got +, -, *, and /. But they differ in how you are required to use them.

Addition (+) and subtraction (-) require both numbers to be lengths

.el {
  /* Valid 👍 */
  margin: calc(10px + 10px);

  /* Invalid 👎 */
  margin: calc(10px + 5);
}

Invalid values invalidate the whole individual declaration.

Division (/) requires the second number to be unitless

.el {
  /* Valid 👍 */
  margin: calc(30px / 3);

  /* Invalid 👎 */
  margin: calc(30px / 10px);

  /* Invalid 👎 (can't divide by 0) */
  margin: calc(30px / 0);
}

Multiplication (*) requires one of the numbers to be unitless

.el {
  /* Valid 👍 */
  margin: calc(10px * 3);

  /* Valid 👍 */
  margin: calc(3 * 10px);

  /* Invalid 👎 */
  margin: calc(30px * 3px);
}

Whitespace matters

Well, it does for addition and subtraction.

.el {
  /* Valid 👍 */
  font-size: calc(3vw + 2px);

  /* Invalid 👎 */
  font-size: calc(3vw+2px);

  /* Valid 👍 */
  font-size: calc(3vw - 2px);

  /* Invalid 👎 */
  font-size: calc(3vw-2px);
}

Negative numbers are OK (e.g. calc(5vw - -5px)), but that’s an example of where the whitespace is not only required, but helpful too.

Tab Atkins tells me that the reason for the required spacing around + and - is actually because of parsing concerns. I can’t say I fully understand it, but for example, 2px-3px is parsed as the number “2” and the unit “px-3px” which isn’t doing anybody any good, and the + has other issues like getting “consumed by the number syntax.” I would have guessed the whitespace would have had to do with the -- syntax of custom properties, but nope!

Multiplication and division do not need the whitespace around the operators. But I’d think good general advice is to include the space for readability and muscle memory for the other operators.

Whitespace around the outsides doesn’t matter. You can even do line breaks if you’d like:

.el {
  /* Valid 👍 */
  width: calc(
    100%     /   3
  );
}

Careful about this, though: no spaces between calc() and the opening paren.

.el {
  /* Invalid 👎 */
  width: calc (100% / 3);
}

Nesting calc(calc());

You can but it’s never necessary. It’s the same as using an extra set of parentheses without the calc() part. For example:

.el {
  width: calc(
    calc(100% / 3)
    -
    calc(1rem * 2)
  );
}

You don’t need those inside calc() because the parens work alone:

.el {
  width: calc(
   (100% / 3)
    -
   (1rem * 2)
  );
}

And in this case, the “order of operations” helps us even without the parentheses. That is, division and multiplication happen first (before addition and subtraction), so the parentheses aren’t needed at all. It could be written like this:

.el {
  width: calc(100% / 3 - 1rem * 2);
}

But feel free to use the parens if you feel like it adds clarity. If the order of operations doesn’t work in your favor (e.g. you really need to do the addition or subtraction first), you’ll need parens.

.el {
  /* This */
  width: calc(100% + 2rem / 2);

  /* Is very different from this */
  width: calc((100% + 2rem) / 2);
}

CSS custom properties and calc() 🎉

Other than the amazing ability of calc() to mix units, the next most awesome thing about calc() is using it with custom properties. Custom properties can have values that you then use in a calculation:

html {
  --spacing: 10px;
}

.module {
  padding: calc(var(--spacing) * 2);
}

I’m sure you can imagine a CSS setup where a ton of configuration happens at the top by setting a bunch of CSS custom properties and then letting the rest of the CSS use them as needed.

Custom properties can also reference each other. Here’s an example where some math is used (note the lack of a calc() function at first) and then later applied. (It ultimately has to be inside of a calc().)

html {
  --spacing: 10px;
  --spacing-L: var(--spacing) * 2;
  --spacing-XL: var(--spacing) * 3;
}

.module[data-spacing="XL"] {
  padding: calc(var(--spacing-XL));
}

You may not like that, as you need to remember the calc() where you use the property then, but it’s possible and potentially interesting from a readability perspective.

Custom properties can come from the HTML, which is a pretty darn cool and useful thing sometimes. (See how Splitting.js adds indexes to words/characters as an example.)

<div style="--index: 1;"> ... </div>
<div style="--index: 2;"> ... </div>
<div style="--index: 3;"> ... </div>
div {
  /* Index value comes from the HTML (with a fallback) */
  animation-delay: calc(var(--index, 1) * 0.2s);
}

Adding units later

In case you’re in a situation where it’s easier to store numbers without units, or do math with unit-less numbers ahead of time, you can always wait until you apply the number to add the unit by multiplying by 1 and the unit.

html {
  --importantNumber: 2;
}

.el {
  /* Number stays 2, but it has a unit now */
  padding: calc(var(--importantNumber) * 1rem);
}

Messing with colors

Color format like RGB and HSL have numbers you can mess with using calc(). For example, setting some base HSL values and then altering them forming a system of your own creation (example):

html {
  --H: 100;
  --S: 100%;
  --L: 50%;
}

.el {
  background: hsl(
    calc(var(--H) + 20),
    calc(var(--S) - 10%),
    calc(var(--L) + 30%)
  )
}

You can’t combine calc() and attr()

The attr() function in CSS looks appealing, like you can pull attribute values out of HTML and use them. But…

<div data-color="red">...</div>
div {
  /* Nope */
  color: attr(data-color);
}

Unfortunately, there are no “types” in play here, so the only thing attr() is for are strings in conjunction with the content property. That means this works:

div::before {
  content: attr(data-color);
}

I mention this because it might be tempting to try to pull a number in that way to use in a calculation, like:

<div class="grid" data-columns="7" data-gap="2">...</div>
.grid {
  display: grid;

  /* Neither of these work */
  grid-template-columns: repeat(attr(data-columns), 1fr);
  grid-gap: calc(1rem * attr(data-gap));
}

Fortunately, it doesn’t matter much because custom properties in the HTML are just as useful or more!

<div class="grid" style="--columns: 7; --gap: 2rem;">...</div>
.grid {
  display: grid;

  /* Yep! */
  grid-template-columns: repeat(var(--columns), 1fr);
  grid-gap: calc(var(--gap));
}

Browser tooling

Browser DevTools will tend you show you the calc() as you authored it in the stylesheet.

Firefox DevTools – Rules

If you need to figure out the computed value, there is a Computed tab (in all browser DevTools, at least the ones I know about) that will show it to you.

Chrome DevTools – Computed

Browser support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
19*4*11126*

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1261271266.0-6.1*

If you really needed to support super far back (e.g. IE 8 or Firefox 3.6), the usual trick is to add another property or value before the one that uses calc():

.el {
  width: 92%; /* Fallback */
  width: calc(100% - 2rem);
}

There are quite a few known issues for calc() as well, but they are all for old browsers. Can I use… lists 13 of them. Here’s a handful:

  • Firefox <59 does not support calc() on color functions. Example: color: hsl(calc(60 * 2), 100%, 50%).
  • IE 9-11 will not render the box-shadow property when calc() is used for any of the values.
  • Neither IE 9-11 nor Edge support width: calc() on table cells.

Use-case party

I asked some CSS developers when they last used calc() so we could have a nice taste here for how others use it in their day-to-day work.

I used it to create a full-bleed utility class: .full-bleed { width: 100vw; margin-left: calc(50% - 50vw); } I’d say calc() is in my top 3 CSS things.

I used it to make space for a sticky footer.

I used it to set some fluid type / dynamic typography… a calculated font-size based on minimums, maxiums, and a rate of change from viewport units. Not just the font-size, but line-height too.

If you’re using calc() as part of a fluid type situation that involves viewport units and such, make sure that you include a unit that uses rem or em so that the user still has some control over bumping that font up or down by zooming in or out as they need to.

One I really like is having a “content width” custom property and then using that to create the spacing that I need, like margins: .margin { width: calc( (100vw - var(--content-width)) / 2); }

I used it to create a cross-browser drop-cap component. Here’s a part of it:

.drop-cap { --drop-cap-lines: 3; font-size: calc(1em * var(--drop-cap-lines) * var(--body-line-height)); }

I used it to make some images overflow their container on an article page.

I used it to place a visualization correctly on the page by combining it with padding and vw/vh units.

I use it to overcome limitations in background-position, but expecially limitations in positioning color stops in gradients. Like “stop 0.75em short of the bottom”.

Other trickery


A Complete Guide to calc() in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-complete-guide-to-calc-in-css/feed/ 4 303923
Keep Math in the CSS https://css-tricks.com/keep-math-in-the-css/ https://css-tricks.com/keep-math-in-the-css/#comments Wed, 12 Dec 2018 17:22:11 +0000 http://css-tricks.com/?p=279313 There is a sentiment that leaving math calculations in your CSS is a good idea that I agree with. This is for math that you could calculate at authoring time, but specifically chose not to. For instance, if you needed …


Keep Math in the CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There is a sentiment that leaving math calculations in your CSS is a good idea that I agree with. This is for math that you could calculate at authoring time, but specifically chose not to. For instance, if you needed a 7-column float-based grid (don’t ask), it’s cleaner and more intuitive:

.col {
  /* groan */
  width: 14.2857142857%;

  /* oh, I get it */
  width: calc(100% / 7);
}

You could probably prove that the calc() takes the computer 0.0000001% longer, so explicitly defining the width is technically faster for performance reason — but that is about the equivalent of not using punctuation in sentences because it saves HTML weight.

That math can be a little more complicated as you continue. For example, like in our use cases for calc() article, what about columns in that 7-column grid that span?

.column-1-7 {
   width: calc(100% / 7);
}

.column-2-7 {
   width: calc(100% / 7 * 2);
}

.column-3-7 {
   width: calc(100% / 7 * 3);
}

I’d say that’s rather clean to read and manage.

The readability of the math can be enhanced by comments if it gets too complicated. Say you are trying to account for a margin-based gutter with padding inside of an element:

.parent {
  width: 600px;
  padding: 18px;
}

.left {
  /* base width - 1/2 horizontal padding */
  width: calc(400px - 18px);
  margin-right: 1rem; /* gutter */
}

.right {
  /* base width - 1/2 horizontal padding - gutter */
  width: calc(200px - 1rem - 18px);
}

Again, I’d say that’s pretty readable, but it’s also a good amount of repetition. This might call for using variables. We’ll do it with CSS custom properties for fun. You have to pick what is worthy of a variable and what isn’t. You might need fewer comments as the code becomes somewhat self-documenting:

.parent {
  --padding: 18px;
  --gutter: 1rem;
  
  width: 600px;
  padding: var(--padding);
}

.left {
  width: calc(400px - var(--padding));
  margin-right: var(--gutter);
}

.right {
  width: calc(200px - var(--gutter) - var(--padding));
}

That is a decent balance to me. Here’s a step further:

.parent {
  --padding: 18px;
  --gutter: 1rem;
  --parentWidth: 600px;
  --leftSize: 2/3;
  --rightSize: 1/3;
  
  width: var(--parentWidth);
  padding: var(--padding);
}

.left {
  width: calc(calc(var(--parentWidth) * var(--leftSize)) - var(--padding));
  margin-right: var(--gutter);
}

.right {
  width: calc(calc(var(--parentWidth) * var(--rightSize)) - var(--gutter) - var(--padding));
}

Every single number has been given a variable in there. Too far? Maybe. It certainly makes those width declarations pretty hard to wrap your head around quickly. Ana Tudor does some serious stuff with calc(), as proof that everyone’s comfort level with this stuff is different.

One of the things that made me think of all this is a recent article from James Nash — “Hardcore CSS calc()” — where he builds this:

While the solution took a heavily math-y road to get there, it ends up being only sort of medium-level calculation on the ol’ complexity meter. And note that not everything gets a variable’ only the most re-used bits:

See the Pen Fluid 1 + 2 thumbnail block by James Nash (@cirrus) on CodePen.


Keep Math in the CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/keep-math-in-the-css/feed/ 8 279313
Computer Science Distilled, Chapter 2: Complexity https://css-tricks.com/computer-science-distilled-chapter-2-complexity/ https://css-tricks.com/computer-science-distilled-chapter-2-complexity/#comments Tue, 28 Feb 2017 12:59:10 +0000 http://css-tricks.com/?p=251754 This is a full chapter excerpt from Wladston Viana Ferreira Filho's brand new book Computer Science Distilled which he has graciously allowed for us to publish here.

In almost every computation, a variety of arrangements for the processes is possible. It is essential to choose that arrangement which shall tend to minimize the time necessary for the calculation. —Ada Lovelace


Computer Science Distilled, Chapter 2: Complexity originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is a full chapter excerpt from Wladston Viana Ferreira Filho’s brand new book Computer Science Distilled which he has graciously allowed for us to publish here.

In almost every computation, a variety of arrangements for the processes is possible. It is essential to choose that arrangement which shall tend to minimize the time necessary for the calculation.

—Ada Lovelace

How much time does it take to sort 26 shuffled cards? If instead, you had 52 cards, would it take twice as long? How much longer would it take for a thousand decks of cards? The answer is intrinsic to the method used to sort the cards.

A method is a list of unambiguous instructions for achieving a goal. A method that always requires a finite series of operations is called an algorithm. For instance, a card-sorting algorithm is a method that will always specify some operations to sort a deck of 26 cards per suit and per rank.

Less operations need less computing power. We like fast solutions, so we monitor the number of operations in our algorithms. Many algorithms require a fast-growing number of operations when the input grows in size. For example, our card-sorting algorithm could take few operations to sort 26 cards, but four times more operations to sort 52 cards!

To avoid bad surprises when our problem size grows, we find the algorithm’s time complexity. In this chapter, you’ll learn to:

  • Count and interpret time complexities
  • Express their growth with fancy Big-O‘s
  • Run away from exponential algorithms
  • Make sure you have enough computer memory.

But first, how do we define time complexity?

Time complexity is written T(n). It gives the number of operations the algorithm performs when processing an input of size n. We also refer to an algorithm's T(n) as its running cost. If our card-sorting algorithm follows T(n)=n2, we can predict how much longer it takes to sort a deck once we double its size: T(2n)T(n)=4.

Hope for the best, prepare for the worst

Isn't it faster to sort a pile of cards that's almost sorted already?

Input size isn't the only characteristic that impacts the number of operations required by an algorithm. When an algorithm can have different values of T(n) for the same value of n, we resort to cases:

  • Best Case: when the input requires the minimum number of operations for any input of that size. In sorting, it happens when the input is already sorted.
  • Worst Case: when the input requires the maximum number of operations for any input of that size. In many sorting algorithms, that’s when the input was given in reverse order.
  • Average Case: refers to the average number of operations required for typical inputs of that size. For sorting, an input in random order is usually considered.

In general, the most important is the worst case. From there, you get a guaranteed baseline you can always count on. When nothing is said about the scenario, the worst case is assumed. Next, we’ll see how to analyze a worst case scenario, hands on.

Figure 2.1: “Estimating Time”, courtesy of xkcd.com.

2.1 Counting Time

We find the time complexity of an algorithm by counting the number of basic operations it requires for a hypothetical input of size n. We'll demonstrate it with Selection Sort, a sorting algorithm that uses a nested loop. An outer for loop updates the current position being sorted, and an inner for loop selects the item that goes in the current position1:

function selection_sort(list)
    for current ← 1 … list.length - 1
        smallest ← current
        for i ← current + 1 … list.length
            if list[i] < list[smallest]
                smallest ← i
        list.swap_items(current, smallest)

Let's see what happens with a list of n items, assuming the worst case. The outer loop runs n-1 times and does two operations per run (one assignment and one swap) totaling 2n-2 operations. The inner loop first runs n-1 times, then n-2 times, n-3 times, and so on. We know how to sum these types of sequences2:
number of inner loop runs= n1   +   n2++2+1n1total runs of the outer loop.
= i=1n1i=(n1)(n)2=n2n2.

In the worst case, the if condition is always met. This means the inner loop does one comparison and one assignment (n2-n)/2 times, hence n2-n operations. In total, the algorithm costs 2n-2 operations for the outer loop, plus n2-n operations for the inner loop. We thus get the time complexity:

T(n)=n2+n2.

Now what? If our list size was n=8 and we double it, the sorting time will be multiplied by:

T(16)T(8)=162+16282+823.86.

If we double it again we will multiply time by 3.90. Double it over and over and find 3.94, 3.97, 3.98. Notice how this gets closer and closer to 4? This means it would take four times as long to sort two million items than to sort one million items.

2.1.1 Understanding Growth

Say the input size of an algorithm is very large, and we increase it even more. To predict how the execution time will grow, we don't need to know all terms of T(n). We can approximate T(n) by its fastest-growing term, called the dominant term.

The Index Card Problem: Yesterday, you knocked over one box of index cards. It took you two hours of Selection Sort to fix it. Today, you spilled ten boxes. How much time will you need to arrange the cards back in?

We've seen Selection Sort follows T(n)=n2+n-2. The fastest-growing term is n2, therefore we can write T(n)n2. Assuming there are n cards per box, we find:

T(10n)T(n)(10n)2n2=100.

It will take you approximately (100×2)hours=200 hours! What if we had used a different sorting method? For example, there’s one called "Bubble Sort" whose time complexity is T(n)=0.5n2+0.5n. The fastest-growing term then gives T(n)0.5n2, hence:

T(10n)T(n)0.5×(10n)20.5×n2=100.
Figure 2.2: Zooming out n2,  n2+n-2,  and  0.5n2+0.5n,  as n gets larger and larger.

The 0.5 coefficient cancels itself out! The idea that n2-n-2 and 0.5n2+0.5n both grow like n2 isn't easy to get. How does the fastest-growing term of a function ignore all other numbers and dominate growth? Let’s try to visually understand this.

In Figure 2.2, the two time complexities we've seen are compared to n2 at different zoom levels. As we plot them for larger and larger values of n, their curves seem to get closer and closer. Actually, you can plug any numbers into the bullets of T(n)=n2+n+, and it will still grow like n2.

Remember, this effect of curves getting closer works if the fastest-growing term is the same. The plot of a function with a linear growth (n) never gets closer and closer to one with a quadratic growth (n2), which in turn never gets closer and closer to one having a cubic growth (n3).

That’s why with very big inputs, algorithms with a quadratically growing cost perform a lot worse than algorithms with a linear cost. However, they perform a lot better than those with a cubic cost. If you’ve understood this, the next section will be easy: we will just learn the fancy notation coders use to express this.

2.2 The Big-O Notation

There's a special notation to refer to classes of growth: the Big-O notation. A function with a fastest-growing term of 2n or weaker is O(2n); one with a quadratic or weaker growth is O(n2); growing linearly or less, O(n), and so on. The notation is used for expressing the dominant term of algorithms' cost functions in the worst case—that's the standard way of expressing time complexity3.

Figure 2.3: Different orders of growth often seen inside O.

Both Selection Sort and Bubble Sort are O(n2), but we'll soon discover O(nlogn) algorithms that do the same job. With our O(n2) algorithms, 10× the input size resulted in 100× the running cost. Using a O(nlogn) algorithm, 10× the input size results in only 10log1034× the running cost.

When n is a million, n2 is a trillion, whereas nlogn is just a few million. Years running a quadratic algorithm on a large input could be equivalent to minutes if a O(nlogn) algorithm was used. That’s why you need time complexity analysis when you design systems that handle very large inputs.

When designing a computational system, it’s important to anticipate the most frequent operations. Then you can compare the Big-O costs of different algorithms that do these operations4. Also, most algorithms only work with specific input structures. If you choose your algorithms in advance, you can structure your input data accordingly.

Some algorithms always run for a constant duration regardless of input size—they're O(1). For example, checking if a number is odd or even: we see if its last digit is odd and boom, problem solved. No matter how big the number. We'll see more O(1) algorithms in the next chapters. They're amazing, but first let's see which algorithms are not amazing.

2.3 Exponentials

We say O(2n) algorithms are exponential time. From the graph of growth orders (Figure 2.3), it doesn't seem the quadratic n2 and the exponential 2n are much different. Zooming out the graph, it's obvious the exponential growth brutally dominates the quadratic one:

Figure 2.4: Different orders of growth, zoomed out. The linear and logarithmic curves grow so little they aren’t visible anymore.

Exponential time grows so much, we consider these algorithms “not runnable”. They run for very few input types, and require huge amounts of computing power if inputs aren’t tiny. Optimizing every aspect of the code or using supercomputers doesn’t help. The crushing exponential always dominates growth and keeps these algorithms unviable.

To illustrate the explosiveness of exponential growth, let's zoom out the graph even more and change the numbers (Figure 2.5). The exponential was reduced in power (from 2 to 1.5) and had its growth divided by a thousand. The polynomial had its exponent increased (from 2 to 3) and its growth multiplied by a thousand.

Figure 2.5: No exponential can be beaten by a polynomial. At this zoom level, even the nlogn curve grows too little to be visible.

Some algorithms are even worse than exponential time algorithms. It's the case of factorial time algorithms, whose time complexities are O(n!). Exponential and factorial time algorithms are horrible, but we need them for the hardest computational problems: the famous NP-complete problems. We will see important examples of NP-complete problems in the next chapter. For now, remember this: the first person to find a non-exponential algorithm to a NP-complete problem gets a million dollars5 from the Clay Mathematics Institute.

It’s important to recognize the class of problem you’re dealing with. If it’s known to be NP-complete, trying to find an optimal solution is fighting the impossible. Unless you’re shooting for that million dollars.

2.4 Counting Memory

Even if we could perform operations infinitely fast, there would still be a limit to our computing power. During execution, algorithms need working storage to keep track of their ongoing calculations. This consumes computer memory, which is not infinite.

The measure for the working storage an algorithm needs is called space complexity. Space complexity analysis is similar to time complexity analysis. The difference is that we count computer memory, and not computing operations. We observe how space complexity evolves when the algorithm’s input size grows, just as we do for time complexity.

For example, Selection Sort just needs working storage for a fixed set of variables. The number of variables does not depend on the input size. Therefore, we say Selection Sort's space complexity is O(1): no matter what the input size, it requires the same amount of computer memory for working storage.

However, many other algorithms need working storage that grows with input size. Sometimes, it's impossible to meet an algorithm’s memory requirements. You won't find an appropriate sorting algorithm with O(nlogn) time complexity and O(1) space complexity. Computer memory limitations sometimes force a tradeoff. With low memory, you’ll probably need an algorithm with slow O(n2) time complexity because it has O(1) space complexity.

Conclusion

In this chapter, we learned algorithms can have different types of voracity for consuming computing time and computer memory. We’ve seen how to assess it with time and space complexity analysis. We learned to calculate time complexity by finding the exact T(n) function, the number of operations performed by an algorithm.

We've seen how to express time complexity using the Big-O notation (O). Throughout this book, we'll perform simple time complexity analysis of algorithms using this notation. Many times, calculating T(n) is not necessary for inferring the Big-O complexity of an algorithm.

We’ve seen the cost of running exponential algorithms explode in a way that makes these algorithms not runnable for big inputs. And we learned how to answer these questions:

  • Given different algorithms, do they have a significant difference in terms of operations required to run?
  • Multiplying the input size by a constant, what happens with the time an algorithm takes to run?
  • Would an algorithm perform a reasonable number of operations once the size of the input grows?
  • If an algorithm is too slow for running on an input of a given size, would optimizing the algorithm, or using a supercomputer help?

1: To understand an new algorithm, run it on paper with a small sample input.

2: In the previous chapter, we showed i=1ni=n(n+1)/2.

3: We say ‘oh’, e.g., “that sorting algorithm is oh-n-squared“.

4: For the Big-O complexities of most algorithms that do common tasks, see http://code.energy/bigo

5: It has been proven a non-exponential algorithm for any NP-complete problem could be generalized to all NP-complete problems. Since we don’t know if such an algorithm exists, you also get a million dollars if you prove an NP-complete problem cannot be solved by non-exponential algorithms!


Computer Science Distilled: Learn the Art of Solving Computational Problems by Wladston Viana Ferreira Filho is available on Amazon now.


Computer Science Distilled, Chapter 2: Complexity originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/computer-science-distilled-chapter-2-complexity/feed/ 6 251754
Some Math Links https://css-tricks.com/some-math-links/ https://css-tricks.com/some-math-links/#comments Fri, 03 Feb 2017 14:46:57 +0000 http://css-tricks.com/?p=250906 I read a few stories about math lately. One of them is a story by Kevin Hartnett about Francis Su, who believes math can be a gateway to a better life. To Live Your Best Life, Do Mathematics.:

Christopher,


Some Math Links originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I read a few stories about math lately. One of them is a story by Kevin Hartnett about Francis Su, who believes math can be a gateway to a better life. To Live Your Best Life, Do Mathematics.:

Christopher, an inmate serving a long sentence for armed robbery who had begun to teach himself math from textbooks he had ordered. After seven years in prison, during which he studied algebra, trigonometry, geometry and calculus, he wrote to Su asking for advice on how to continue his work. After Su told this story, he asked the packed ballroom at the Marriott Marquis, his voice breaking: “When you think of who does mathematics, do you think of Christopher?”

If I learn mathematics and I become a better thinker, I develop perseverance, because I know what it’s like to wrestle with a hard problem, and I develop hopefulness that I will actually solve these problems. And some people experience a kind of transcendent wonder that they’re seeing something true about the universe. That’s a source of joy and flourishing.

And another by Jinju Jang, The story of a designer conquering mathematics.:

 I was quite proud of my artwork. It looked interesting, saved so much time compared to creating the same design in Illustrator or Photoshop. However, I realised without understanding geometry, there was no chance for me to reach somewhere near to Marius Watz.

Jinju slowly leveled up her math skills, applying it to everything she did along the way.

Of course, this all reminds me of Ana Tudor, who is an unconventional (and at times self-deprecating) mathematician, who uses her considerable math skills to create art with code.

And also a wonderful recent talk by Natalya Shelburne, Color Theory for people who code SVG and CSS. Here’s a million dollar quote, very slightly paraphrased:

If someone says to you, don’t you miss doing creative stuff, as an engineer? Don’t you miss the creativity of the art world? You can say: I’m working on an infinite canvas capable of any size and shape, that’s already glowing every color possible into the eyes of just about everyone on the planet. And I get to decide the kind of experience, feeling, and emotion they are going to have. How is that not creative?

More on color theory from our own Sarah Drasner: A Nerd’s Guide to Color on the Web


Some Math Links originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/some-math-links/feed/ 2 250906