Skapa en klocka med trigonometrifunktionerna New CSS sin() och cos().

Skapa en klocka med trigonometrifunktionerna New CSS sin() och cos().

Källnod: 1999799

CSS-trigonometrifunktioner är här! Tja, det är de om du använder de senaste versionerna av Firefox och Safari, det vill säga. Att ha den här typen av matematisk kraft i CSS öppnar upp en hel massa möjligheter. I den här handledningen tänkte jag att vi skulle doppa tårna i vattnet för att få en känsla för ett par av de nyare funktionerna: sin() och cos().

Det finns andra trigonometrifunktioner i pipeline - inklusive tan() – så varför fokusera bara på sin() och cos()? De råkar vara perfekta för idén jag har i åtanke, som är att placera text längs kanten på en cirkel. Det har tagits upp här på CSS-Tricks när Chris delade ett tillvägagångssätt som använder en Sass-mixin. Det var för sex år sedan, så låt oss ge det den blödande kanten-behandlingen.

Här är vad jag har i åtanke. Återigen, det stöds bara i Firefox och Safari för tillfället:

Så det är inte precis som ord som bildar en cirkulär form, men vi placerar texttecken längs cirkeln för att bilda en urtavla. Här är några markeringar vi kan använda för att sätta igång saker:

<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>

Nästa, här är några super grundläggande stilar för .clock-face behållare. Jag bestämde mig för att använda <time> tagg med en datetime attribut. 

.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);
}

Jag dekorerade saker lite där inne, men bara för att få grundformen och bakgrundsfärgen för att hjälpa oss att se vad vi gör. Lägg märke till hur vi sparar width värde i a CSS-variabel. Vi använder det senare. Inte mycket att titta på än så länge:

Stor tomatfärgad cirkel med en vertikal lista med nummer 1-12 till vänster.

Det ser ut som något slags modern konstexperiment, eller hur? Låt oss introducera en ny variabel, --_r, för att lagra cirkelns radius, vilket är lika med hälften av cirkelns bredd. På detta sätt, om bredden (--_w) ändras, radievärdet (--_r) kommer också att uppdateras - tack vare en annan CSS-mattefunktion, calc():

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

Nu lite matematik. En cirkel är 360 grader. Vi har 12 etiketter på vår klocka, så vill placera siffrorna var 30:e grader (360 / 12). I matteland börjar en cirkel vid 3-tiden, så middag är faktiskt minus 90 grader från det, vilket är 270 grader (360 - 90).

Låt oss lägga till en annan variabel, --_d, som vi kan använda för att ställa in en grad värde för varje nummer på urtavlan. Vi kommer att öka värdena med 30 grader för att slutföra vår cirkel:

.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, nu är det dags att smutsa ner händerna med sin() och cos() funktioner! Vad vi vill göra är att använda dem för att få X- och Y-koordinaterna för varje nummer så att vi kan placera dem på rätt sätt dygnet runt.

Formeln för X-koordinaten är radius + (radius * cos(degree)). Låt oss koppla in det i vår nya --_x variabel:

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

Formeln för Y-koordinaten är radius + (radius * sin(degree)). Vi har vad vi behöver för att beräkna det:

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

Det finns några hushållssaker vi behöver göra för att ställa in siffrorna, så låt oss sätta lite grundläggande stil på dem för att se till att de är absolut placerade och placerade med våra koordinater:

.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);
}

Lägga märke till --_sz, som vi kommer att använda för width och height av siffrorna på ett ögonblick. Låt oss se vad vi har hittills.

Stor tomatfärgad cirkel med ocentrerade timnummeretiketter längs kanten.

Det här ser definitivt mer ut som en klocka! Ser du hur det övre vänstra hörnet av varje nummer är placerat på rätt plats runt cirkeln? Vi måste "krympa" radien när vi beräknar positionerna för varje nummer. Vi kan dra av storleken på ett tal (--_sz) från cirkelns storlek (--_w), innan vi beräknar radien:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Stor tomatfärgad cirkel med timnummeretiketter längs den rundade kanten.

Mycket bättre! Låt oss ändra färgerna så att det ser mer elegant ut:

En vit urtavla med siffror mot en mörkgrå bakgrund. Klockan har inga armar.

Vi kunde sluta här! Vi uppnådde målet att placera text runt en cirkel, eller hur? Men vad är en klocka utan armar för att visa timmar, minuter och sekunder?

Låt oss använda en enda CSS-animation för det. Låt oss först lägga till ytterligare tre element till vår uppmärkning,

<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>

Sedan lite gemensam markering för alla tre armarna. Återigen, det mesta av detta är bara att se till att armarna är absolut placerade och placerade därefter:

.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);
}

Vi använder samma animation för alla tre armarna:

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

Den enda skillnaden är den tid det tar för de enskilda armarna att göra en hel varv. För timmar arm, det tar 12 timmar att göra en hel sväng. De animation-duration egenskapen accepterar endast värden i millisekunder och sekunder. Låt oss hålla oss till sekunder, vilket är 43,200 XNUMX sekunder (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Det tar 1 timme för minuter arm att göra en hel sväng. Men vi vill att detta ska vara en flerstegsanimering så rörelsen mellan armarna är förskjuten snarare än linjär. Vi behöver 60 steg, ett för varje minut:

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

Smakämnen sekunder arm is nästan samma som minutarmen, men varaktigheten är 60 sekunder istället för 60 minuter:

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

Låt oss uppdatera egenskaperna vi skapade i de vanliga stilarna:

.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;
}

Vad händer om vi vill börja vid den aktuella tiden? Vi behöver lite 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`);

jag har lagt till id="app" till urtavlan och ställ in två nya anpassade egenskaper på den som sätter ett negativ animation-delay, som Mate Marschalko gjorde när han delade en CSS-klocka. De getHours() metod för JavaScipt Date objektet använder 24-timmarsformatet, så vi använder remainder Operatören för att konvertera den till 12-timmarsformat.

I CSS måste vi lägga till animation-delay också:

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

Bara en sak till. Använda CSS @supports och egenskaperna vi redan har skapat, kan vi erbjuda en reserv till webbläsare som inte stöder sin() och cos(). (Tack, 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)))
  }
}

Och voilà! Vår klocka är klar! Här är den sista demon en gång till. Återigen, det stöds bara i Firefox och Safari för tillfället.

Vad mer kan vi göra?

Bråkar bara här, men vi kan snabbt förvandla vår klocka till ett cirkulärt bildgalleri genom att byta ut den <time> taggar med <img> uppdaterar sedan bredden (--_w) och radie (--_r) värden:

Låt oss prova en till. Jag nämnde tidigare hur klockan såg ut som ett modernt konstexperiment. Vi kan luta oss in i det och återskapa ett mönster jag såg på en affisch (som jag tyvärr inte köpte) i ett konstgalleri häromdagen. Som jag minns, kallades den "Månen" och bestod av ett gäng prickar som bildade en cirkel.

En stor cirkel bildades av ett gäng mindre fyllda cirklar i olika jordtonsfärger.

Vi kommer att använda en oordnad lista den här gången eftersom cirklarna inte följer en viss ordning. Vi kommer inte ens att lägga in alla listobjekt i markeringen. Låt oss istället injicera dem med JavaScript och lägga till några kontroller som vi kan använda för att manipulera det slutliga resultatet.

Kontrollerna är intervallingångar (<input type="range">) som vi lindar in i en <form> och lyssna efter input händelse.

<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>

Vi kommer att köra den här metoden på "input", vilket kommer att skapa en massa <li> element med graden (--_d) variabel som vi använde tidigare tillämpad på var och en. Vi kan också återanvända vår radievariabel (--_r).

Jag vill också att prickarna ska ha olika färger. Så låt oss randomisera (nåja, inte fullständigt randomiserat) HSL-färgvärdet för varje listobjekt och lagra det som en ny CSS-variabel, --_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;
}

Smakämnen random() metoden väljer ett värde inom ett definierat intervall av tal:

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

Och det är allt. Vi använder JavaScript för att rendera uppmärkningen, men så fort den har renderats behöver vi den inte riktigt. De sin() och cos() funktioner hjälper oss att placera alla prickar på rätt ställen.

Avslutande tankar

Att placera saker runt en cirkel är ett ganska grundläggande exempel för att demonstrera krafterna hos trigonometrifunktioner som sin() och cos(). Men det är verkligen coolt att vi får moderna CSS-funktioner som ger nya lösningar för gamla lösningar. Jag är säker på att vi kommer att se mycket mer intressanta, komplexa och kreativa användningsfall, särskilt när webbläsarstöd kommer till Chrome och Edge.

Tidsstämpel:

Mer från CSS-tricks