Создание часов с помощью новых тригонометрических функций 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> тег с 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, чтобы сохранить круг radius, что равно половине ширины круга. Таким образом, если ширина (--_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, который мы можем использовать для установки степень значение для каждого числа на циферблате. Мы собираемся увеличить значения на 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 часа сделать полный оборот. animation-duration свойство принимает значения только в миллисекундах и секундах. Давайте остановимся на секундах, что составляет 43,200 XNUMX секунд (60 seconds * 60 minutes * 12 hours).

animation: turn 43200s infinite;

Он принимает 1 час для минутная рука сделать полный оборот. Но мы хотим, чтобы это было многоступенчатая анимация поэтому движение между руками скорее ступенчатое, чем прямолинейное. Нам понадобится 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">) который мы завернем в <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 для рендеринга разметки, но как только он рендерится, он нам на самом деле не нужен. sin() и cos() функции помогают нам расположить все точки в правильных местах.

Заключение

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

Отметка времени:

Больше от CSS хитрости