Время переменных

Виталий Зюзин
5 min readFeb 27, 2018

--

В этой статье я расскажу, как собрал демку с использованием кастомных свойств CSS (они же CSS-переменные) и по ходу проапгрейдил своё мышление в контексте CSS.

Вёрстка аналоговых часов не то чтобы оригинальная идея, но она показалась мне удобным способом показать особенности использования кастомных свойств.

Итак, вот что вышло:

Вот как это устроено:

  • JS генерирует данные о текущем времени и отправляет их в HTML и CSS;
  • HTML использует эти данные для отображения времени в текстовом виде;
  • CSS переводит числа в визуально понятный человеку графический вид.

Итак, начнём с разметки.

<time class="clock">
<span class="clock__hand clock__hand--hour"></span>:
<span class="clock__hand clock__hand--minute"></span>:
<span class="clock__hand clock__hand--second"></span>
<svg class="clock__face" aria-hidden="true" role="presentation">
<circle class="clock__face-stroke"></circle>
</svg>
</time>

Общий контейнер часов <time>, три стрелки и декоративная SVG для отрисовки циферблата. Сразу продумаем доступность:

  • в спанах стрелок с помощью JavaScript будет записываться текущее время числами;
  • элементу <time> будет задано соответсвующее значение атрибута datetime;
  • декоративный элемент <svg> скроем от скринридеров атрибутом aria-hidden и снимем с него смысловую нагрузку с помощью атрибута role.

Теперь CSS. Я не буду описывать весь код демки (лучше с ней поиграть вживую), а остановлюсь только на ключевых моментах.

Определим настройки внешнего вида часов — диаметр и толщину рамки часов:

.clock {
--clock-diameter: 250px;
--clock-border-width: 5px;
}

Тут же используем эти переменные:

.clock {  
width: var(--clock-diameter);
height: var(--clock-diameter);
border: var(--clock-border-width) solid #000000;
}

Пока всё просто.

Теперь стрелки часов. Сначала определим ширину и высоту стрелок и спозиционируем их по центру циферблата. По умолчанию стрелки будут шириной 1px и высотой 50%. В зависимости от ширины и высоты абсолютно спозиционированных стрелок вычисляются их координаты top и left:

.clock__hand {
--hand-width: 1px;
--hand-height: 50%;

position: absolute;
top: calc(50% - var(--hand-height));
left: calc(50% - calc(var(--hand-width) / 2));
width: var(--hand-width);
height: var(--hand-height);
}
/* С кастомными свойствами можно производить арифметические операции с помощью функции calc() */

Осталось определить цвет стрелок и задать им поворот вокруг оси часов. Стрелки часов изначально будут чёрного цвета и в не повёрнутом состоянии:

.clock__hand {
background-color: var(--hand-color, #000000);
transform: rotate(var(--turn, 0turn));
}
/* При использовании кастомного свойства можно задать
фолбэк-значение */

И теперь переопределим в модификаторах блоков характеристики стрелок:

.clock__hand--hour {
--hand-width: 6px;
--hand-height: 30%;
}
.clock__hand--minute {
--hand-width: 4px;
--hand-height: 40%;
}
.clock__hand--second {
--hand-width: 2px;
--hand-height: 45%;
--hand-color: red;
}

Вот что получилось:

Развернём стрелки для наглядности:

.clock__hand--hour {
--turn: 0.25turn;
}
.clock__hand--minute {
--turn: 0.6turn;
}
.clock__hand--second {
--turn: 0.8turn;
}
/* Полный поворот круга — 1turn, да, есть такая единица измерения */

Текущее время будем тоже хранить в трёх кастомных свойствах:

.clock {
--hours: 10;
--minutes: 30;
--seconds: 15;
}

Теперь встаёт вопрос, как перевести часы, минуты и секунды в единицы поворота круга. И тут на помощь приходит школьная математика.

Начнём с секундной стрелки. Секунд в полном обороте круга 60 — это соответствует значению поворота 1turn. А нам нужно узнать, сколько turn в n-секунде. Получается такая пропорция:

xturn - nсек
1turn - 60сек

То есть x = 1turn * nсек / 60сек . Переведём эти вычисления в CSS:

.clock__hand--second {
--seconds-in-minute: 60;
--turn: calc(1turn * var(--seconds) / var(--seconds-in-minute));
}

Проверим, вот что получилось:

Всё ок, 15 секунд, как и задано в --seconds .

Далее минутная стрелка. Тут всё точно так же, как и у секундной — в часе 60 минут:

.clock__hand--second {
--minutes-in-hour: 60;
--turn: calc(1turn * var(--minutes) / var(--minutes-in-hour));
}

Картинка подтверждает, что всё ок:

И осталась часовая стрелка. В обороте круга 12 часов:

.clock__hand--hour {
--hours-in-day-half: 12;
--turn: calc(1turn * var(--hours) / var(--hours-in-day-half));
}

Всё работает, но хочется, чтобы часовая стрелка двигалась ещё дополнительно в течение часа в зависимости от минут, а не просто раз в час перескакивала на следующее деление:

Поэтому для часовой стрелки давайте рассчитаем дополнительный поворот в пределах одного часа по тому же принципу, что и раньше.

Снова составим пропорцию: 1/12 turn — это 1 час или 60 минут. Нам нужно узнать, какое значение turn будет на n-минуте часа.

xturn   - nмин
1/12turn - 60мин

То есть x = 1/12turn * nмин / 60мин . Переведём эти вычисления в CSS и сложим основной поворот часовой стрелки с дополнительным:

.clock__hand--hour {
--hours-in-day-half: 12;
--hours-turn: calc(1turn * var(--hours) / var(--hours-in-day-half));

--min-in-hour: 60;
--minutes-turn: calc((1 / var(--hours-in-day-half)) * 1turn * var(--minutes) / var(--min-in-hour));

--turn: calc(var(--hours-turn) + var(--minutes-turn));
}

Получилось именно то, что нужно:

А вот так это работает в браузере:

Теперь оживим получившуюся конструкцию реальными данными.

В JS будем каждую секунду задавать значения нашим кастомным свойствам --hours , --minutes и --seconds :

const clock = document.querySelector('.clock');const setCustomProperty = (name, value) => {
clock.style.setProperty(`--${name}`, value);
};
const setTimeVariables = (time) => {
setCustomProperty('hours', time.getHours());
setCustomProperty('minutes', time.getMinutes());
setCustomProperty('seconds', time.getSeconds());
};
const setTime = () => setTimeVariables(new Date());setTime();
setInterval(setTime, 1000);

Не забудем про скринридеры и доступность. Для этого в HTML будем обновлять текстовый контент элементов в теге <time> и его атрибут datetime:

const clock = document.querySelector('.clock');
const hoursBox = document.querySelector('.clock__hand--hour');
const minutesBox = document.querySelector('.clock__hand--minute');
const secondsBox = document.querySelector('.clock__hand--second');
const setTimeLayout = (time) => {
hoursBox.textContent = time.getHours();
minutesBox.textContent = time.getMinutes();
secondsBox.textContent = time.getSeconds();

clock.setAttribute('datetime', time.toISOString());
}
const setTime = () => setTimeLayout(new Date());setTime();
setInterval(setTime, 1000);

Вот как это выглядит живьём в дев-тулз:

В общем, попробовав раз, хочется дальше повсеместно использовать кастомные свойства, настолько органичны они оказались в использовании. Останавливает сейчас только поддержка IE (в Edge кастомные свойства уже работают в последних двух версиях). Но для свежих проектов и демок — это маст-хэв!

--

--