Tworzenie zegara za pomocą nowych funkcji trygonometrii CSS sin() i cos().

Tworzenie zegara za pomocą nowych funkcji trygonometrii CSS sin() i cos().

Węzeł źródłowy: 1999799

Funkcje trygonometrii CSS są tutaj! Cóż, są, jeśli używasz najnowszych wersji przeglądarek Firefox i Safari. Posiadanie tego rodzaju matematycznej mocy w CSS otwiera całą masę możliwości. Pomyślałem, że w tym samouczku zanurzymy palce w wodzie, aby zapoznać się z kilkoma nowszymi funkcjami: sin() i cos().

W przygotowaniu są inne funkcje trygonometrii — w tym tan() — więc po co skupiać się tylko na sin() i cos()? Tak się składa, że ​​idealnie pasują do pomysłu, który mam na myśli, czyli umieszczania tekstu wzdłuż krawędzi okręgu. Zostało to omówione tutaj w CSS-Tricks kiedy Chris podzielił się podejściem wykorzystującym miks Sass. To było sześć lat temu, więc dajmy temu krwawiące traktowanie.

Oto, co mam na myśli. Ponownie, jest to obecnie obsługiwane tylko w przeglądarkach Firefox i Safari:

Nie jest to więc dokładnie tak, jak słowa tworzące okrągły kształt, ale umieszczamy znaki tekstowe wzdłuż okręgu, tworząc tarczę zegara. Oto kilka znaczników, których możemy użyć, aby rozpocząć:

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

Następnie, oto kilka bardzo podstawowych stylów dla .clock-face pojemnik. Zdecydowałem się skorzystać z tzw <time> tag z a datetime atrybutów. 

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

Trochę tam udekorowałem, ale tylko po to, aby uzyskać podstawowy kształt i kolor tła, które pomogą nam zobaczyć, co robimy. Zwróć uwagę, jak zapisujemy plik width wartość w A Zmienna CSS. Wykorzystamy to później. Na razie niewiele do oglądania:

Duże koło w kolorze pomidora z pionową listą liczb 1-12 po lewej stronie.

Wygląda to na jakiś eksperyment ze sztuką współczesną, prawda? Wprowadźmy nową zmienną, --_r, aby zapisać koło promień, co jest równe połowie szerokości koła. W ten sposób, jeśli szerokość (--_w) zmienia się, wartość promienia (--_r) również zostanie zaktualizowany — dzięki kolejnej funkcji matematycznej CSS, calc():

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

A teraz trochę matematyki. Koło ma 360 stopni. Mamy 12 etykiet na naszym zegarze, więc chcemy umieścić cyfry co 30 stopni (360 / 12). W krainie matematyki koło zaczyna się o godzinie 3, a więc tak naprawdę jest to południe minus 90 stopni od tego, który wynosi 270 stopni (360 - 90).

Dodajmy kolejną zmienną, --_d, którego możemy użyć do ustawienia a stopień wartość dla każdej liczby na tarczy zegara. Zwiększymy wartości o 30 stopni, aby zamknąć nasz okrąg:

.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, teraz jest czas, aby ubrudzić sobie ręce sin() i cos() Funkcje! To, co chcemy zrobić, to użyć ich do uzyskania współrzędnych X i Y dla każdej liczby, abyśmy mogli odpowiednio umieścić je na tarczy zegara.

Wzór na współrzędną X to radius + (radius * cos(degree)). Podłączmy to do naszego nowego --_x zmienna:

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

Wzór na współrzędną Y to radius + (radius * sin(degree)). Mamy to, czego potrzebujemy, aby to obliczyć:

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

Jest kilka czynności porządkowych, które musimy wykonać, aby ustawić liczby, więc nałóżmy na nie kilka podstawowych stylów, aby upewnić się, że są dokładnie ustawione i umieszczone zgodnie z naszymi współrzędnymi:

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

Ogłoszenie --_sz, którego użyjemy do width i height liczby za chwilę. Zobaczmy, co mamy do tej pory.

Duże koło w kolorze pomidora z umieszczonymi wzdłuż krawędzi etykietami z numerami godzinowymi poza środkiem.

To zdecydowanie bardziej przypomina zegar! Zobacz, jak lewy górny róg każdej liczby jest umieszczony we właściwym miejscu wokół okręgu? Musimy „zmniejszyć” promień podczas obliczania pozycji dla każdej liczby. Możemy odjąć rozmiar liczby (--_sz) od rozmiaru koła (--_w), zanim obliczymy promień:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Duże koło w kolorze pomidora z etykietami z liczbą godzin wzdłuż zaokrąglonej krawędzi.

Dużo lepiej! Zmieńmy kolory, żeby wyglądało to bardziej elegancko:

Biała tarcza zegara z cyframi na ciemnoszarym tle. Zegar nie ma ramion.

Moglibyśmy zatrzymać się tutaj! Osiągnęliśmy cel, jakim było umieszczenie tekstu wokół okręgu, prawda? Ale co to za zegar bez wskazówek, który pokazuje godziny, minuty i sekundy?

Użyjmy do tego pojedynczej animacji CSS. Najpierw dodajmy jeszcze trzy elementy do naszego znacznika,

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

Następnie niektóre wspólne znaczniki dla wszystkich trzech ramion. Ponownie, większość z tego to po prostu upewnienie się, że ramiona są absolutnie ustawione i odpowiednio ustawione:

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

Użyjemy ta sama animacja dla wszystkich trzech ramion:

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

Jedyną różnicą jest czas potrzebny poszczególnym ramionom na wykonanie pełnego obrotu. Dla ramię godzin, to trwa 12 godzin wykonać pełny obrót. The animation-duration właściwość akceptuje tylko wartości w milisekundach i sekundach. Pozostańmy przy sekundach, czyli 43,200 XNUMX sekund (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

To trwa 1 godzin dla ramię minuty wykonać pełny obrót. Ale chcemy, żeby to było animacja wieloetapowa więc ruch między ramionami jest raczej schodkowy niż liniowy. Będziemy potrzebować 60 kroków, po jednym na każdą minutę:

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

Połączenia ramię sekundy is prawie to samo jak ramię minut, ale czas trwania wynosi 60 sekund zamiast 60 minut:

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

Zaktualizujmy właściwości, które stworzyliśmy we wspólnych stylach:

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

A co jeśli chcemy zacząć od aktualnego czasu? Potrzebujemy trochę 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`);

dodałem id="app" do tarczy zegara i ustaw na niej dwie nowe niestandardowe właściwości, które ustawiają wartość ujemną animation-delay, jak zrobił to Mate Marschalko kiedy udostępnił zegar obsługujący tylko CSS,  getHours() metoda JavaScipt Date obiekt używa formatu 24-godzinnego, więc używamy formatu remainder operator aby przekonwertować go na format 12-godzinny.

W CSS musimy dodać animation-delay także:

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

Jeszcze jedna rzecz. Korzystanie z CSS @supports i właściwości, które już stworzyliśmy, możemy zapewnić rozwiązanie awaryjne dla przeglądarek, które nie obsługują sin() i cos(). (Dziękuję, Temani Afifie!):

@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)))
  }
}

I voila! Nasz zegar się skończył! Oto ostateczne demo jeszcze raz. Ponownie, jest to obecnie obsługiwane tylko w przeglądarkach Firefox i Safari.

Co jeszcze możemy zrobić?

Po prostu się tu bawimy, ale możemy szybko zamienić nasz zegar w okrągłą galerię obrazów, zastępując plik <time> tagi z <img> następnie aktualizując szerokość (--_w) i promień (--_r) wartości:

Spróbujmy jeszcze jednego. Wspomniałem wcześniej, że zegar wyglądał jak eksperyment ze sztuką współczesną. Możemy się nad tym pochylić i odtworzyć wzór, który widziałem na plakacie (którego niestety nie kupiłem) w galerii sztuki. O ile pamiętam, nazywał się „Księżyc” i składał się z wiązki kropek tworzących okrąg.

Duże koło utworzone z kilku mniejszych wypełnionych kół o różnych kolorach ziemi.

Tym razem użyjemy listy nieuporządkowanej, ponieważ kręgi nie mają określonej kolejności. Nie będziemy nawet umieszczać wszystkich elementów listy w znacznikach. Zamiast tego wstrzyknijmy do nich JavaScript i dodajmy kilka kontrolek, których możemy użyć do manipulowania końcowym wynikiem.

Elementami sterującymi są wejścia zakresu (<input type="range">) które zawiniemy w a <form> i posłuchaj o input zdarzenie.

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

Uruchomimy tę metodę na „input”, co spowoduje utworzenie pęczka <li> elementy ze stopniem (--_d) zmienna, której użyliśmy wcześniej, zastosowała do każdego z nich. Możemy również zmienić przeznaczenie naszej zmiennej promienia (--_r).

Chcę też, aby kropki były różnych kolorów. Więc losujmy (cóż, nie całkowicie losowo) wartość koloru HSL dla każdego elementu listy i zapisać ją jako nową zmienną CSS, --_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;
}

Połączenia random() Metoda wybiera wartość ze zdefiniowanego zakresu liczb:

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

I to wszystko. Używamy JavaScript do renderowania znaczników, ale gdy tylko zostanie wyrenderowany, tak naprawdę go nie potrzebujemy. The sin() i cos() pomagają nam umieścić wszystkie kropki we właściwych miejscach.

Końcowe przemyślenia

Umieszczanie przedmiotów wokół koła jest dość prostym przykładem demonstrującym moc funkcji trygonometrii, takich jak sin() i cos(). Ale to jest naprawdę fajnie, że otrzymujemy nowoczesne funkcje CSS, które zapewniają nowe rozwiązania dla starych obejść. Jestem pewien, że zobaczymy o wiele bardziej interesujące, złożone i kreatywne przypadki użycia, zwłaszcza gdy Chrome i Edge otrzymają obsługę przeglądarek.

Znak czasu:

Więcej z Sztuczki CSS