Створення годинника за допомогою нових тригонометричних функцій CSS sin() і cos().

Створення годинника за допомогою нових тригонометричних функцій CSS sin() і cos().

Вихідний вузол: 1999799

Тригонометричні функції CSS тут! Ну, якщо ви використовуєте останні версії Firefox і Safari, тобто. Наявність такої математичної потужності в CSS відкриває цілий ряд можливостей. У цьому посібнику я думав, що ми зануримо пальці ніг у воду, щоб відчути пару новіших функцій: sin() та cos().

У конвеєрі є й інші тригонометричні функції — в тому числі tan() — то навіщо зосереджуватися лише на sin() та cos()? Вони ідеально підходять для ідеї, яку я маю на увазі, тобто розмістити текст уздовж краю кола. Це було розглянуто тут, на CSS-Tricks коли Кріс поділився підходом, який використовує міксин Sass. Це було шість років тому, тож давайте надамо цьому найкраще лікування.

Ось що я маю на увазі. Знову ж таки, наразі він підтримується лише у Firefox і Safari:

Отже, це не зовсім так, як слова, які утворюють форму кола, але ми розміщуємо текстові символи вздовж кола, щоб утворити циферблат годинника. Ось деяка розмітка, яку ми можемо використати, щоб розпочати роботу:

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

Далі, ось кілька супербазових стилів для .clock-face контейнер. Я вирішив скористатися <time> тег з a datetime атрибут. 

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

Я трохи прикрасив речі, але лише для того, щоб отримати основну форму та колір фону, щоб ми могли бачити, що ми робимо. Зверніть увагу, як ми зберігаємо width значення в а Змінна CSS. Ми використаємо це пізніше. Поки що мало на що дивитися:

Велике коло томатного кольору з вертикальним списком цифр 1-12 ліворуч.

Схоже на якийсь сучасний мистецький експеримент, чи не так? Давайте введемо нову змінну, --_r, щоб зберегти кола радіус, що дорівнює половині ширини кола. Таким чином, якщо ширина (--_w) змінюється значення радіуса (--_r) також буде оновлено — завдяки іншій математичній функції CSS, calc():

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

А тепер трохи математики. Коло становить 360 градусів. У нас на годиннику 12 міток, тому ми хочемо розмістити цифри через кожні 30 градусів (360 / 12). У математиці коло починається о 3 годині, отже насправді полудень мінус 90 градусів від того, що становить 270 градусів (360 - 90).

Давайте додамо ще одну змінну, --_d, який ми можемо використовувати для встановлення a ступінь значення для кожного числа на циферблаті годинника. Ми збираємося збільшити значення на 30 градусів, щоб завершити наше коло:

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

Добре, тепер час забруднити руки sin() та cos() функції! Те, що ми хочемо зробити, це використати їх, щоб отримати координати X і Y для кожного числа, щоб ми могли правильно розмістити їх навколо циферблата годинника.

Формула для координати X така radius + (radius * cos(degree)). Давайте підключимо це до нашого нового --_x змінна:

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

Формула для координати Y має вигляд radius + (radius * sin(degree)). У нас є те, що нам потрібно, щоб обчислити, що:

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

Щоб налаштувати числа, нам потрібно зробити кілька речей, тож давайте додамо до них базовий стиль, щоб переконатися, що вони точно розташовані та розміщені з нашими координатами:

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

повідомлення --_sz, який ми будемо використовувати для width та height чисел за мить. Давайте подивимося, що ми маємо на даний момент.

Велике коло томатного кольору зі зміщеними по центру мітками з номерами годин уздовж краю.

Це точно більше схоже на годинник! Бачите, як верхній лівий кут кожної цифри розташований у правильному місці навколо кола? Нам потрібно «зменшити» радіус під час обчислення позицій для кожного числа. Ми можемо відрахувати розмір числа (--_sz) від розміру кола (--_w), перш ніж обчислити радіус:

--_r: calc((var(--_w) - var(--_sz)) / 2);
Велике коло томатного кольору з мітками з номерами годин уздовж заокругленого краю.

Значно краще! Давайте змінимо кольори, щоб це виглядало більш елегантно:

Білий циферблат із цифрами на темно-сірому фоні. Годинник не має стрілок.

Ми могли б зупинитися прямо тут! Ми досягли мети розмістити текст по колу, чи не так? Але що таке годинник без стрілок, щоб показувати години, хвилини та секунди?

Давайте для цього використаємо одну анімацію CSS. Спочатку додамо ще три елементи до нашої розмітки,

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

Потім деяка спільна розмітка для всіх трьох рук. Знову ж таки, більша частина цього полягає в тому, щоб переконатися, що руки абсолютно правильно розташовані та розміщені відповідно:

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

Ми будемо використовувати така сама анімація для всіх трьох рук:

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

Єдина відмінність полягає в часі, який потрібен окремим рукам, щоб зробити повний оберт. Для години арм, потрібно 12 годин щоб зробити повний оборот. The animation-duration властивість приймає значення лише в мілісекундах і секундах. Давайте зупинимося на секундах, які становлять 43,200 XNUMX секунд (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Він приймає 1 годину для хв щоб зробити повний оборот. Але ми хочемо, щоб це було a багатокрокова анімація тому рух між руками є шаховим, а не лінійним. Нам знадобиться 60 кроків, по одному на кожну хвилину:

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

Команда секундна рука is майже те саме як хвилинна рука, але тривалість становить 60 секунд замість 60 хвилин:

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

Давайте оновимо властивості, які ми створили в загальних стилях:

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

Що, якщо ми хочемо почати в поточний час? Нам потрібно трохи 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`);

Я додав id="app" до циферблата годинника та встановіть для нього дві нові власні властивості, які встановлюють мінус animation-delay, як це зробив Мате Маршалко коли він поділився годинником лише з CSS,  getHours() метод JavaScipt Date об’єкт використовує 24-годинний формат, тому ми використовуємо remainder оператор щоб конвертувати його в 12-годинний формат.

У CSS нам потрібно додати animation-delay так само:

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

Тільки ще одне. Використання CSS @supports і властивостей, які ми вже створили, ми можемо надати резервний варіант для браузерів, які не підтримують sin() та  cos(). (Дякую, Темані Афіф!):

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

І вуаля! Наш годинник готовий! Ось ще раз остання демонстрація. Знову ж таки, наразі він підтримується лише у Firefox і Safari.

Що ми ще можемо зробити?

Ми просто возимося, але ми можемо швидко перетворити наш годинник на круглу галерею зображень, замінивши <time> теги з <img> потім оновлюючи ширину (--_w) і радіус (--_r) значення:

Давайте спробуємо ще один. Раніше я згадував, що годинник виглядав як експеримент сучасного мистецтва. Ми можемо спертися на це й відтворити візерунок, який я бачив на плакаті (який, на жаль, не купив) у художній галереї днями. Наскільки я пам'ятаю, він називався «Місяць» і складався з купи крапок, що утворюють коло.

Велике коло, утворене з купи менших заповнених кіл різних кольорів земних тонів.

Цього разу ми використаємо невпорядкований список, оскільки кола не дотримуються певного порядку. Ми навіть не збираємося розміщувати всі елементи списку в розмітці. Замість цього давайте введемо їх за допомогою JavaScript і додамо кілька елементів керування, які ми зможемо використовувати для керування кінцевим результатом.

Елементи керування – це вхід діапазону (<input type="range">) який ми загорнемо в a <form> і слухати input події.

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

Ми запустимо цей метод на «вхід», що створить групу <li> елементи зі ступенем (--_d) змінна, яку ми використовували раніше, застосована до кожного. Ми також можемо змінити призначення нашої змінної радіуса (--_r).

Я також хочу, щоб точки були різних кольорів. Отже, давайте рандомізуємо (ну, ні повністю рандомізовано) значення кольору HSL для кожного елемента списку та збережіть його як нову змінну 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;
}

Команда random() метод вибирає значення в межах визначеного діапазону чисел:

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

І це все. Ми використовуємо JavaScript для відтворення розмітки, але щойно вона відтворюється, вона нам не потрібна. The sin() та cos() функції допомагають нам розташувати всі крапки в потрібних місцях.

Заключні думки

Розміщення предметів по колу є досить простим прикладом для демонстрації можливостей таких тригонометричних функцій, як sin() та cos(). Але це насправді Чудово, що ми отримуємо сучасні функції CSS, які надають нові рішення для старих обхідних шляхів. Я впевнений, що ми побачимо набагато цікавіші, складніші та креативніші випадки використання, особливо коли підтримка браузерів з’явиться в Chrome і Edge.

Часова мітка:

Більше від CSS-хитрощі