Что здесь не так?
Здравствуйте, недавно послушал новый выпуск Веб-стандартов и там был момент с обсуждением статьи «Время переменных» где автор решил поэкспериментировать с CSS переменными и создать на основе них аналоговые часы. Все выглядит шикарно и главное работает, но у меня появилось много вопросов и я решил немного поэкспериментировать и заодно вам рассказать о своих умозаключениях.
Не подумайте ничего плохого, я люблю CSS переменные и все новое в CSS, но как всегда есть пару но. Во первых, мне лично очень не нравиться идея писать в DOM каждую секунду, ведь все что связано с ним достаточно ресурсозатратно, но тут я не совсем уверен насколько это может быть плохо. Во вторых, не смотря на то что CSS переменные очень удобно использовать, все же их нужно использовать с умом, ведь каждый раз при изменении переменной вы вызываете перерисовку каждого элемента, который ее использует. И тут я вижу главную проблему для себя.
Вкладка Rendering в консоли Chrome помогла подтвердить факт перерисовки:
Мы видим перерисовку каждую секунду и средний fps 50 кадров и это на странице, где у нас изображены всего одни часы.
А как еще можно?
И так я поставил перед собой задачу создать аналоговые часы, который не будут вызывать перерисовку страницы и полагаться на JavaScript для вычисления положения стрелки. Часы которые не будут никак влиять на частоту кадров в браузере. Среди всех мне известных CSS свойств, только одно позволяет трансформировать элемент без перерисовки — transform. Значит его и будем использовать.
Но сначала создадим свои часы, с которыми нам будем удобно шевелить стрелку одним только transform свойством. Создадим циферблат со всеми стрелками:
Идея тут такая — если поместить каждую стрелку в отдельный контейнер, который будет в центре часов, то вращая этот контейнер относительно центра мы будем вращать и саму стрелку. Вот пример стилей такого контейнера для часовой стрелки:
.clock__hand {
margin-left: -0.5em;
margin-top: -0.5em;
font-size: inherit;
position: absolute;
display: block;
height: 1em;
width: 1em;
left: 50%;
top: 50%;
}
.clock__hand--hour::after {
content: "";
border-radius: 0.015em 0.015em 0.01em 0.01em;
background-color: #000;
margin-bottom: -0.02em;
margin-left: -0.025em;
font-size: inherit;
position: absolute;
display: block;
height: 0.25em;
width: 0.05em;
bottom: 50%;
left: 50%;
}
С помощью font-size в данном случае мы задаем размер циферблата и всех компонентов часов.
Код вида самых часов.
Хорошо, а на сколько вращать?
Вращать стрелку на все 360 градус. Правильный вопрос: как долго ее вращать? А зависит это от того, какую стрелку мы вращаем. Часовую — 12 часов, минутную — час, секундную — минуту.
.clock__hand--hour {
animation: clock-hand-rotate 43200s linear infinite;
}
.clock__hand--minute {
animation: clock-hand-rotate 3600s linear infinite;
}
.clock__hand--second {
animation: clock-hand-rotate 60s linear infinite;
}
@keyframes clock-hand-rotate {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
И так наши часы заработали.
А что же нам скажет про них Chrome?
Никаких перерисовок и стабильных 60 fps
Но это же не наше время.
И так наше начальное время 00:00:00 потому что все стрелки начинают анимацию с 0 градусов. Чтобы начинать с настоящего времени нам нужно рассчитывать начальный градус отдельно для каждой стрелки относительно времени. И так у нас два варианта, либо на стороне сервера рендерить CSS относительно времени запроса, либо использовать JavaScript. Конечно сервер рендеринг и без скриптовые часы это круто, но ради подтверждения концепта мы все же используем JavaScript.
var date = new Date(),
hours = date.getHours(),
minutes = date.getMinutes(),
seconds = date.getSeconds();
if (hours > 12) {
hours -= 12;
}
var secondsStartDegree = 360 / 60 * seconds,
minutesStartDegree = 360 / 60 * minutes + 6 / 60 * seconds,
hoursStartDegree = 360 / 12 * hours + 30 / 60 * minutes + 0.5 / 60 * seconds;
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '\
@keyframes clock-hand-rotate--hour {\
from {transform: rotate(' + hoursStartDegree + 'deg)}\
to {transform: rotate(' + (hoursStartDegree + 360) + 'deg)}\
}\
@keyframes clock-hand-rotate--minute {\
from {transform: rotate(' + minutesStartDegree + 'deg)}\
to {transform: rotate(' + (minutesStartDegree + 360) + 'deg)}\
}\
@keyframes clock-hand-rotate--second {\
from {transform: rotate(' + secondsStartDegree + 'deg)}\
to {transform: rotate(' + (secondsStartDegree + 360) + 'deg)}\
}\
.clock__hand--hour {\
animation: clock-hand-rotate--hour 43200s linear infinite;\
}\
.clock__hand--minute {\
animation: clock-hand-rotate--minute 3600s linear infinite;\
}\
.clock__hand--second {\
animation: clock-hand-rotate--second 60s steps(60) infinite;\
}';
document.getElementsByTagName('head')[0].appendChild(style);
И там мы создаем 3 анимации для каждой из стрелок и подключаем их к соответственным классам.
Вот результат.
А что еще можно?
Ну если вам не хватает того факта, что у вас часы которые почти не забирают ресурсов браузера. То вот еще пару фишек:
- Часы с прыгающей минутной и секундной стрелками
- Часов с разными часовыми поясами
- Также все работает в Internet Explorer 11 и мобильных браузерах
Хорошо а в чем минусы
Их не так уж много, но они есть:
- Так как часы отсчитывают время CSS-ом, то если забить тред какими-то тяжёлыми заданиями — часы перестанут идти и когда страница освободится — будут отставать. Но все это можно поправить заново задав keyframe.
- Не всюду сайт будет выглядеть идеально, вот например в том же IE11 некоторые центры немного смещены:
Итоги
Не знаю как на счет использования этих часов в продакшене, но я сделал для себя лично выводы относительно новых технологий.
И я бы сказал, что не всегда стоит кидаться за новыми технологиями, если и старые еще могут не мало. А самое главное — лучше хорошо использовать надежные инструменты, чем просто пробовать всюду всунуть новую невероятную фичу языка.
Да и в плане ресурсов браузера, никогда бы не подумал, что простая прорисовка заберет столько кадров.
Надеюсь вам тоже было интересно, спасибо за прочтение.