Friday, March 29, 2024
HomeProgrammingMaking a Clock with the New CSS sin() and cos() Trigonometry Features...

Making a Clock with the New CSS sin() and cos() Trigonometry Features | CSS-Tips


CSS trigonometry features are right here! Nicely, they’re in case you’re utilizing the most recent variations of Firefox and Safari, that’s. Having this form of mathematical energy in CSS opens up a complete bunch of potentialities. On this tutorial, I assumed we’d dip our toes within the water to get a really feel for a few the newer features: sin() and cos().

There are different trigonometry features within the pipeline — together with tan() — so why focus simply on sin() and cos()? They occur to be excellent for the concept I keep in mind, which is to put textual content alongside the sting of a circle. That’s been lined right here on CSS-Tips when Chris shared an method that makes use of a Sass mixin. That was six years in the past, so let’s give it the bleeding edge therapy.

Right here’s what I keep in mind. Once more, it’s solely supported in Firefox and Safari for the time being:

So, it’s not precisely like phrases forming a round form, however we’re inserting textual content characters alongside the circle to kind a clock face. Right here’s some markup we are able to use to kick issues 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>

Subsequent, listed here are some tremendous primary types for the .clock-face container. I made a decision to make use of 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;
  show: grid;
  top: var(--_ow);
  place-content: heart;
  place: relative;
  width var(--_ow);
}

I adorned issues a bit in there, however solely to get the essential form and background colour to assist us see what we’re doing. Discover how we save the width worth in a CSS variable. We’ll use that later. Not a lot to have a look at to date:

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

It seems to be like some form of fashionable artwork experiment, proper? Let’s introduce a brand new variable, --_r, to retailer the circle’s radius, which is the same as half of the circle’s width. This manner, if the width (--_w) adjustments, the radius worth (--_r) may also replace — thanks to a different CSS math operate, calc():

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

Now, a little bit of math. A circle is 360 levels. We have now 12 labels on our clock, so need to place the numbers each 30 levels (360 / 12). In math-land, a circle begins at 3 o’clock, so midday is definitely minus 90 levels from that, which is 270 levels (360 - 90).

Let’s add one other variable, --_d, that we are able to use to set a diploma worth for every quantity on the clock face. We’re going to increment the values by 30 levels to finish 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 arms soiled with the sin() and cos() features! What we need to do is use them to get the X and Y coordinates for every quantity so we are able to place them correctly across the clock face.

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

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

The formulation for the Y coordinate is radius + (radius * sin(diploma)). We have now what we have to calculate that:

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

There are a couple of housekeeping issues we have to do to arrange the numbers, so let’s put some primary styling on them to ensure they’re completely positioned and positioned 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;
  show: grid;
  top: var(--_sz);
  left: var(--_x);
  place-content: heart;
  place: absolute;
  prime: var(--_y);
  width: var(--_sz);
}

Discover --_sz, which we’ll use for the width and top of the numbers in a second. Let’s see what we’ve got to date.

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

This positively seems to be extra like a clock! See how the top-left nook of every quantity is positioned on the appropriate place across the circle? We have to “shrink” the radius when calculating the positions for every quantity. We will deduct the dimensions of a quantity (--_sz) from the dimensions of the circle (--_w), earlier than we calculate the radius:

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

Significantly better! Let’s change the colours, so it seems to be extra elegant:

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

We might cease proper right here! We achieved the objective of inserting textual content round a circle, proper? However what’s a clock with out arms to point out hours, minutes, and seconds?

Let’s use a single CSS animation for that. First, let’s add three extra components 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 heart"></span>
</div>

Then some frequent markup for all three arms. Once more, most of that is simply be certain that the arms are completely positioned and positioned accordingly:

.arm {
  background-color: var(--_abg);
  border-radius: calc(var(--_aw) * 2);
  show: block;
  top: var(--_ah);
  left: calc((var(--_w) - var(--_aw)) / 2);
  place: absolute;
  prime: calc((var(--_w) / 2) - var(--_ah));
  remodel: rotate(0deg);
  transform-origin: backside;
  width: var(--_aw);
}

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

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

The one distinction is the time the person arms take to make a full flip. For the hours arm, it takes 12 hours to make a full flip. The animation-duration property solely accepts values in milliseconds and seconds. Let’s keep on with seconds, which is 43,200 seconds (60 seconds * 60 minutes * 12 hours).

animation: flip 43200s infinite;

It takes 1 hour for the minutes arm to make a full flip. However we wish this to be a multi-step animation so the motion between the arms is staggered quite than linear. We’ll want 60 steps, one for every minute:

animation: flip 3600s steps(60, finish) infinite;

The seconds arm is virtually the identical because the minutes arm, however the length is 60 seconds as an alternative of 60 minutes:

animation: flip 60s steps(60, finish) infinite;

Let’s replace the properties we created within the frequent types:

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

What if we need to begin on the present time? We want a little bit little bit of JavaScript:

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

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

Within the CSS, we have to add the animation-delay as properly:

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

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

Only one other thing. Utilizing CSS @helps and the properties we’ve already created, we are able to present a fallback to browsers that don’t supprt sin() and cos(). (Thanks, Temani Afif!):

@helps not (left: calc(1px * cos(45deg))) {
  time {
    left: 50% !necessary;
    prime: 50% !necessary;
    remodel: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
  }
}

And, voilà! Our clock is finished! Right here’s the ultimate demo yet one more time. Once more, it’s solely supported in Firefox and Safari for the time being.

What else can we do?

Simply messing round right here, however we are able to shortly flip our clock right into a round picture gallery by changing the <time> tags with <img> then updating the width (--_w) and radius (--_r) values:

Let’s strive yet one more. I discussed earlier how the clock appeared form of like a contemporary artwork experiment. We will lean into that and re-create a sample I noticed on a poster (that I sadly didn’t purchase) in an artwork gallery the opposite day. As I recall, it was referred to as “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 listing this time because the circles don’t observe a specific order. We’re not even going to place all of the listing objects within the markup. As an alternative, let’s inject them with JavaScript and add a couple of controls we are able to use to control the ultimate end result.

The controls are vary inputs (<enter sort="vary">) which we’ll wrap in a <kind> and hear for the enter occasion.

<kind id="controls">
  <fieldset>
    <label>Variety of rings
      <enter sort="vary" min="2" max="12" worth="10" id="rings" />
    </label>
    <label>Dots per ring
      <enter sort="vary" min="5" max="12" worth="7" id="dots" />
    </label>
    <label>Unfold
      <enter sort="vary" min="10" max="40" worth="40" id="unfold" />
    </label>
  </fieldset>
</kind>

We’ll run this technique on “enter”, which is able to create a bunch of <li> components with the diploma (--_d) variable we used earlier utilized to every one. We will additionally repurpose our radius variable (--_r) .

I additionally need the dots to be totally different colours. So, let’s randomize (properly, not utterly randomized) the HSL colour worth for every listing merchandise and retailer it as a brand new CSS variable, --_bgc:

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

The random() technique picks a price inside an outlined vary of numbers:

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

And that’s it. We use JavaScript to render the markup, however as quickly because it’s rendered, we don’t really want it. The sin() and cos() features assist us place all of the dots in the proper spots.

Remaining ideas

Putting issues round a circle is a fairly primary instance to reveal the powers of trigonometry features like sin() and cos(). Nevertheless it’s actually cool that we’re getting fashionable CSS options that present new options for outdated workarounds I’m positive we’ll see far more attention-grabbing, complicated, and inventive use instances, particularly as browser help involves Chrome and Edge.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments