Erstellen einer Uhr mit den neuen CSS-Trigonometriefunktionen sin() und cos()

Erstellen einer Uhr mit den neuen CSS-Trigonometriefunktionen sin() und cos()

Quellknoten: 1999799

CSS-Trigonometriefunktionen sind da! Nun, das sind sie, wenn Sie die neuesten Versionen von Firefox und Safari verwenden. Diese Art von mathematischer Kraft in CSS eröffnet eine ganze Reihe von Möglichkeiten. In diesem Tutorial dachte ich, wir würden unsere Zehen ins Wasser tauchen, um ein Gefühl für ein paar der neueren Funktionen zu bekommen: sin() und cos().

Es sind weitere Trigonometriefunktionen in der Pipeline – einschließlich tan() – warum also nur darauf konzentrieren sin() und cos()? Sie sind zufällig perfekt für die Idee, die ich im Sinn habe, nämlich Text am Rand eines Kreises zu platzieren. Das wurde hier auf CSS-Tricks wann behandelt Chris teilte einen Ansatz, der ein Sass-Mixin verwendet. Das war vor sechs Jahren, also lassen Sie es uns auf den neusten Stand bringen.

Hier ist, was ich im Sinn habe. Auch hier wird es derzeit nur in Firefox und Safari unterstützt:

Es ist also nicht genau so, als würden Wörter eine kreisförmige Form bilden, aber wir platzieren Textzeichen entlang des Kreises, um ein Zifferblatt zu bilden. Hier ist ein Markup, mit dem wir beginnen können:

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

Als nächstes sind hier einige super grundlegende Stile für die .clock-face Container. Ich beschloss, die zu verwenden <time> Tag mit einem 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);
}

Ich habe die Dinge dort ein wenig dekoriert, aber nur, um die Grundform und die Hintergrundfarbe zu erhalten, damit wir sehen können, was wir tun. Beachten Sie, wie wir die speichern width Wert in a CSS-Variable. Das verwenden wir später. Bisher nicht viel zu sehen:

Großer tomatenfarbener Kreis mit einer vertikalen Liste von Zahlen 1-12 auf der linken Seite.

Es sieht aus wie eine Art modernes Kunstexperiment, oder? Lassen Sie uns eine neue Variable einführen, --_r, um die Kreise zu speichern radius, was der Hälfte der Breite des Kreises entspricht. Auf diese Weise, wenn die Breite (--_w) ändert, der Radiuswert (--_r) wird ebenfalls aktualisiert – dank einer weiteren CSS-Mathematikfunktion, calc():

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

Jetzt ein bisschen Mathe. Ein Kreis hat 360 Grad. Wir haben 12 Etiketten auf unserer Uhr, also möchten wir die Zahlen alle 30 Grad platzieren (360 / 12). Im Mathe-Land beginnt ein Kreis um 3 Uhr, also ist eigentlich Mittag minus 90 Grad davon 270 Grad (360 - 90).

Lassen Sie uns eine weitere Variable hinzufügen, --_d, mit denen wir a setzen können (...) Wert für jede Zahl auf dem Ziffernblatt. Wir werden die Werte um 30 Grad erhöhen, um unseren Kreis zu vervollständigen:

.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, jetzt ist es an der Zeit, uns die Hände schmutzig zu machen sin() und cos() Funktionen! Was wir tun möchten, ist, sie zu verwenden, um die X- und Y-Koordinaten für jede Zahl zu erhalten, damit wir sie richtig auf dem Zifferblatt platzieren können.

Die Formel für die X-Koordinate lautet radius + (radius * cos(degree)). Stecken wir das in unsere neue --_x Variable:

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

Die Formel für die Y-Koordinate lautet radius + (radius * sin(degree)). Wir haben, was wir brauchen, um das zu berechnen:

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

Es gibt ein paar organisatorische Dinge, die wir tun müssen, um die Nummern einzurichten, also lassen Sie uns ein grundlegendes Styling darauf anwenden, um sicherzustellen, dass sie absolut positioniert und mit unseren Koordinaten platziert sind:

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

Hinweise --_sz, die wir für die verwenden werden width und height der Zahlen in einem Moment. Mal sehen, was wir bisher haben.

Großer tomatenfarbener Kreis mit außermittigen Stundenzahlen am Rand.

Das sieht definitiv eher aus wie eine Uhr! Sehen Sie, wie die obere linke Ecke jeder Zahl an der richtigen Stelle im Kreis positioniert ist? Wir müssen den Radius „schrumpfen“, wenn wir die Positionen für jede Zahl berechnen. Wir können abziehen die Größe einer Zahl (--_sz) von der Größe des Kreises (--_w), bevor wir den Radius berechnen:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Großer tomatenfarbener Kreis mit Stundennummernetiketten entlang der abgerundeten Kante.

Viel besser! Ändern wir die Farben, damit es eleganter aussieht:

Ein weißes Ziffernblatt mit Zahlen vor einem dunkelgrauen Hintergrund. Die Uhr hat keine Arme.

Wir könnten genau hier aufhören! Wir haben das Ziel erreicht, Text um einen Kreis herum zu platzieren, richtig? Aber was ist eine Uhr ohne Zeiger, die Stunden, Minuten und Sekunden anzeigen?

Lassen Sie uns dafür eine einzelne CSS-Animation verwenden. Lassen Sie uns zunächst drei weitere Elemente zu unserem Markup hinzufügen,

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

Dann ein gemeinsames Markup für alle drei Arme. Auch hier ist das meiste nur sicherzustellen, dass die Arme absolut positioniert und entsprechend platziert sind:

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

Wir werden das benutzen gleiche Animation für alle drei Arme:

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

Der einzige Unterschied besteht in der Zeit, die die einzelnen Arme für eine volle Umdrehung benötigen. Für die Stunden arm, es braucht 12 Stunden eine volle Umdrehung zu machen. Der animation-duration -Eigenschaft akzeptiert nur Werte in Millisekunden und Sekunden. Bleiben wir bei Sekunden, das sind 43,200 Sekunden (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Es dauert 1 Stunden für die Minuten Arm eine volle Umdrehung zu machen. Aber wir wollen, dass dies ein mehrstufige Animation Die Bewegung zwischen den Armen ist also eher versetzt als linear. Wir brauchen 60 Schritte, einen für jede Minute:

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

Das Sekunden Arm is fast das gleiche wie der Minutenarm, aber die Dauer beträgt 60 Sekunden statt 60 Minuten:

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

Aktualisieren wir die Eigenschaften, die wir in den gängigen Stilen erstellt haben:

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

Was ist, wenn wir zur aktuellen Zeit beginnen wollen? Wir brauchen ein wenig 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`);

Ich habe hinzugefügt id="app" auf das Zifferblatt und legen Sie zwei neue benutzerdefinierte Eigenschaften darauf fest, die ein Negativ festlegen animation-delay, wie es Mate Marschalko tat als er eine Nur-CSS-Uhr teiltedem „Vermischten Geschmack“. Seine  getHours() Methode von JavaScipt Date Objekt verwendet das 24-Stunden-Format, also verwenden wir die remainder Operator um es in das 12-Stunden-Format umzuwandeln.

Im CSS müssen wir die hinzufügen animation-delay auch:

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

Nur noch eine Sache. Verwenden von CSS @supports und den Eigenschaften, die wir bereits erstellt haben, können wir einen Fallback für Browser bereitstellen, die dies nicht unterstützen sin() und cos(). (Danke, 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)))
  }
}

Und voilà! Unsere Uhr ist fertig! Hier ist noch einmal die letzte Demo. Auch hier wird es derzeit nur in Firefox und Safari unterstützt.

Was können wir sonst noch tun?

Wir spielen hier nur herum, aber wir können unsere Uhr schnell in eine kreisförmige Bildergalerie verwandeln, indem wir die ersetzen <time> Tags mit <img> Dann aktualisieren Sie die Breite (--_w) und Radius (--_r) Werte:

Versuchen wir es noch einmal. Ich habe bereits erwähnt, dass die Uhr wie ein modernes Kunstexperiment aussah. Wir können uns darauf stützen und ein Muster nachbilden, das ich neulich auf einem Poster (das ich leider nicht gekauft habe) in einer Kunstgalerie gesehen habe. Soweit ich mich erinnere, hieß es „Mond“ und bestand aus einer Reihe von Punkten, die einen Kreis bildeten.

Ein großer Kreis, der aus einer Reihe kleiner gefüllter Kreise in verschiedenen Erdfarben gebildet wurde.

Wir verwenden diesmal eine ungeordnete Liste, da die Kreise keiner bestimmten Reihenfolge folgen. Wir werden nicht einmal alle Listenelemente in das Markup aufnehmen. Lassen Sie uns sie stattdessen mit JavaScript injizieren und ein paar Steuerelemente hinzufügen, mit denen wir das Endergebnis manipulieren können.

Die Bedienelemente sind Bereichseingänge (<input type="range">) die wir in a einwickeln werden <form> und höre auf die input Ereignis.

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

Wir führen diese Methode auf "input" aus, wodurch eine Reihe von erstellt wird <li> Elemente mit dem Grad (--_d)-Variable, die wir zuvor verwendet haben, auf jeden angewendet. Wir können auch unsere Radiusvariable (--_r).

Ich möchte auch, dass die Punkte verschiedene Farben haben. Lassen Sie uns also randomisieren (naja, nicht uneingeschränkt randomisiert) den HSL-Farbwert für jedes Listenelement und speichern Sie ihn als neue 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;
}

Das random() -Methode wählt einen Wert innerhalb eines definierten Zahlenbereichs aus:

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

Und das ist es. Wir verwenden JavaScript, um das Markup zu rendern, aber sobald es gerendert ist, brauchen wir es nicht wirklich. Der sin() und cos() Funktionen helfen uns, alle Punkte an den richtigen Stellen zu positionieren.

Abschließende Gedanken

Das Platzieren von Dingen um einen Kreis herum ist ein ziemlich einfaches Beispiel, um die Leistungsfähigkeit von Trigonometriefunktionen wie zu demonstrieren sin() und cos(). Aber es ist wirklich cool, dass wir moderne CSS-Funktionen bekommen, die neue Lösungen für alte Problemumgehungen bieten. Ich bin mir sicher, dass wir viel interessantere, komplexere und kreativere Anwendungsfälle sehen werden, insbesondere wenn Chrome und Edge Browserunterstützung erhalten.

Zeitstempel:

Mehr von CSS-Tricks