Часы (на CSS-анимации и JavaScript)

Перевод статьи Clocks с сайта cssanimation.rocks.

Время пришло. В этой статье мы попробуем решить задачу по созданию и анимированию настенных часов, используя простые CSS-анимации и JavaScript для их запуска.

Перед вами часы, которые мы создадим с помощью HTML, CSS, SVG-фона и небольшого количества JavaScript. Мы будем использовать CSS-анимации или переходы для любых типов движений, а с помощью JavaScript мы будем задавать исходное время и добавим базовые CSS-трансформации.

HTML

Для начала нам понадобится немного HTML.

<article class="clock">
  <div class="hours-container">
    <div class="hours"></div>
  </div>
  <div class="minutes-container">
    <div class="minutes"></div>
  </div>
  <div class="seconds-container">
    <div class="seconds"></div>
  </div>
</article>

Изначально я хотел использовать три элемента (по одному для каждой из стрелок). Но затем передумал и поместил каждую стрелку в элемент-контейнер. Несмотря на то, что более простой HTML-код подошел бы для базовых CSS-анимаций, элементы-контейнеры понадобятся нам, если мы захотим анимировать и устанавливать положение стрелок.

Циферблат

Начнем с обычных часов с круглым циферблатом и простыми стрелками (часовой, минутной и секундной).

.clock {
  border-radius: 50%;
  background: #fff url(/assets/images/posts/clocks/ios_clock.svg) no-repeat center;
  background-size: 88%;
  height: 20em;
  padding-bottom: 31%;
  position: relative;
  width: 20em;
}

.clock.simple:after {
  background: #000;
  border-radius: 50%;
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 5%;
  height: 5%;
  z-index: 10;
}

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

В результате у нас должно получиться что-то вроде этого.

Перед добавлением стрелок нам нужно расположить контейнеры.

.minutes-container, .hours-container, .seconds-container {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Таким образом мы расположим каждый из контейнеров над циферблатом. После этого мы создадим стрелки.

Часовая стрелка

Свойству «position» у каждой стрелки присваивается значение absolute, и она помещается в позицию «12 часов». Начнем с часовой стрелки.

.hours {
  background: #000;
  height: 20%;
  left: 48.75%;
  position: absolute;
  top: 30%;
  transform-origin: 50% 100%;
  width: 2.5%;
}

Я использую значения в процентах, чтобы упростить задачу масштабирования часов. Для этого нужно потратить немного больше сил, но зато результат всегда легко подогнать под размеры области просмотра или уменьшить под экраны мобильных устройств. Мы также переносим точку вращения стрелки в нижнюю часть с помощью свойства transform-origin , что позволит нам поворачивать стрелку.

Минутная стрелка

Минутная стрелка похожа на часовую, но она длиннее и тоньше.

.minutes {
  background: #000;
  height: 40%;
  left: 49%;
  position: absolute;
  top: 10%;
  transform-origin: 50% 100%;
  width: 2%;
}

Секундная стрелка

Секундная стрелка еще тоньше. Кроме того, она находится ниже, выходя за пределы центра. Для этого мы задаем свойству transform-origin значение 80%. Теперь 20% стрелки выходят за пределы центра.

.seconds {
  background: #000;
  height: 45%;
  left: 49.5%;
  position: absolute;
  top: 14%;
  transform-origin: 50% 80%;
  width: 1%;
  z-index: 8;
}

Анимация

Остановившиеся часы показывают правильное время лишь дважды в день. Давайте добавим немного анимации, чтобы наши часы больше походили на настоящие.

Некоторые часы подскакивают каждую секунду, издавая при этом тикающий звук. Некоторые часы тихо мурлычат, а их стрелки движутся очень плавно. Мы попробуем оба варианта. Для начала заставим стрелки двигаться плавно.

Мы можем задать один ключевой кадр с помощью свойства keyframes, чтобы заставить стрелки выполнять оборот в 360 градусов (их начальное положение при этом принимается за 0%).

@keyframes rotate {
  100% {
    transform: rotateZ(360deg);
  }
}

Этот ключевой кадр сообщает элементу, что тот должен выполнить анимацию поворота на 360 градусов, если кадр был применен к элементу с помощью свойства animation . Мы применим к стрелкам временную функцию типа linear (линейная), чтобы заставить их двигаться плавно.

.hours-container {
  animation: rotate 43200s infinite linear;
}
.minutes-container {
  animation: rotate 3600s infinite linear;
}
.seconds-container {
  animation: rotate 60s infinite linear;
}

Часовая стрелка hours установлена на выполнение полного оборота за 12 часов (43 200 секунд). Минутная стрелка делает один оборот за час, а секундная — за одну минуту.

Соберем все вместе, и у нас готова анимация движения!

Если у вас очень острое зрение, то вы даже можете заметить, как движется минутная стрелка. Для полного оборота ей понадобится час, а часовой стрелке для полного оборота понадобится 12 часов.

Секундная стрелка делает оборот за 60 секунд, поэтому ее движение заметить проще.

Добавление "шагов"

Чтобы движение наших стрелок больше напоминало настоящие часы, мы можем заставить секундную стрелку делать полный оборот за 60 отдельных движений. Сделать это просто. Используем временную функцию steps (шаги). В этом случае свойство animation для каждой из стрелок будет выглядеть следующим образом:

.minutes-container {
  animation: rotate 3600s infinite steps(60);
}
.seconds-container {
  animation: rotate 60s infinite steps(60);
}

Теперь и минутная, и секундная стрелки выполняют полный оборот за 60 шагов. Браузер автоматически рассчитывает, насколько большим должен быть каждый из этих 60 шагов.

Установка правильного времени

Конечно, здорово, когда часы выглядят симпатично, но как заставить их показывать правильное время? Добавив немного JavaScript, мы можем показывать нашим пользователям корректное время. Вот нужный нам код.

/*
 * Устанавливает точку отсчета любых часов в соответствии с местным временем пользователя
 * С сайта: cssanimation.rocks/clocks
 */
function initLocalClocks() {
  // Узнать местное время с помощью JS
  var date = new Date;
  var seconds = date.getSeconds();
  var minutes = date.getMinutes();
  var hours = date.getHours();

  // Создать объект, хранящий все стрелки и их углы в градусах
  var hands = [
    {
      hand: 'hours',
      angle: (hours * 30) + (minutes / 2)
    },
    {
      hand: 'minutes',
      angle: (minutes * 6)
    },
    {
      hand: 'seconds',
      angle: (seconds * 6)
    }
  ];
  // С помощью цикла установить угол для каждой из стрелок
  for (var j = 0; j < hands.length; j++) {
    var elements = document.querySelectorAll('.' + hands[j].hand);
    for (var k = 0; k < elements.length; k++) {
        elements[k].style.webkitTransform = 'rotateZ('+ hands[j].angle +'deg)';
        elements[k].style.transform = 'rotateZ('+ hands[j].angle +'deg)';
        // Если это минутная стрелка, сохранить положение секундной стрелки (для дальнейшего расчета положения минутной стрелки)
        if (hands[j].hand === 'minutes') {
          elements[k].parentNode.setAttribute('data-second-angle', hands[j + 1].angle);
        }
    }
  }
}

Данная функция преобразует время (часы, минуты и секунды) в соответствующий угол для каждой из стрелок. После этого мы в цикле устанавливаем каждой из стрелок нужную величину угла с помощью свойства style.transform со значением rotateZ.

Этот подход сработает в большинстве браузеров, кроме Сафари и тех браузеров, которые требуют использования браузерных префиксов. Для их поддержки мы используем свойство style.webkitTransform .

Таким образом мы устанавливаем часам текущее системное время.

Установка положения секундной и минутной стрелок

Нам нужно убедиться, что движение минутной стрелки происходит, когда секундная стрелка проходит отметку "12 часов".

twelve

Когда часы впервые отрисовываются на экране, время до следующего движения минутной стрелки меньше одной минуты. Для решения этой задачи мы можем рассчитать, сколько секунд осталось до конца этой минуты, и вручную передвинуть стрелку. Раз уж мы использовали JavaScript для первого движения стрелки, мы можем и дальше поворачивать ее на шесть градусов каждую минуту с помощью метода setInterval.

Перед тем, как мы передвинем минутную стрелку, нам нужно сообщить, сколько прошло секунд с начала текущей минуты. Вы могли обратить внимание на эти строки.

if (degrees[j].hand === 'minutes') {
  elements[k].parentNode.setAttribute('data-second-angle', degrees[j + 1].degree);
}

Они нужны для проверки, является ли стрелка «минутной», и если да, то ей устанавливается атрибут «data» с текущим углом секундной стрелки.

Установив атрибут «data», мы можем использовать эти данные, чтобы вычислить, когда нужно передвинуть минутную стрелку.

/*
 * Установить таймаут для первого движения минутной стрелки (меньше 1 минуты), после чего поворачивать ее каждую минуту
 */
function setUpMinuteHands() {
  // Вычислить, сколько секунд прошло с начала этой минуты
  var containers = document.querySelectorAll('.minutes-container');
  var secondAngle = containers[0].getAttribute("data-second-angle");
  if (secondAngle > 0) {
    // Установить таймаут до конца текущей минуты, чтобы передвинуть стрелку
    var delay = (((360 - secondAngle) / 6) + 0.1) * 1000;
    setTimeout(function() {
      moveMinuteHands(containers);
    }, delay);
  }
}

/*
 * Выполнить первый поворот минутной стрелки
 */
function moveMinuteHands(containers) {
  for (var i = 0; i < containers.length; i++) {
    containers[i].style.webkitTransform = 'rotateZ(6deg)';
    containers[i].style.transform = 'rotateZ(6deg)';
  }
  // После этого продолжить с интервалом в 60 секунд
  setInterval(function() {
    for (var i = 0; i < containers.length; i++) {
      if (containers[i].angle === undefined) {
        containers[i].angle = 12;
      } else {
        containers[i].angle += 6;
      }
      containers[i].style.webkitTransform = 'rotateZ('+ containers[i].angle +'deg)';
      containers[i].style.transform = 'rotateZ('+ containers[i].angle +'deg)';
    }
  }, 60000);
}

Добавление отскока

Поскольку для перемещения минутной стрелки мы теперь используем JavaScript, нам следует убрать свойство 'animation'. Вместо того, чтобы просто его удалить, мы можем заменить его на 'transition'. Это позволит нам добавить в анимацию немного оригинальности.

В момент установки стрелке нового угла с помощью JavaScript, CSS-переход элемента будет заставлять браузер анимировать его новое положение. Это значит, что JavaScript будет заниматься лишь простым изменением угла, а браузер займется его анимацией.

Перед тем как сделать это, нам нужно усовершенствовать код, чтобы начать использовать JavaScript и для перемещения секундной стрелки. Давайте используем этот код для анимации контейнера секундной стрелки 60 раз в минуту.

/*
 * Перемещение контейнера секундной стрелки
 */
function moveSecondHands() {
  var containers = document.querySelectorAll('.seconds-container');
  setInterval(function() {
    for (var i = 0; i < containers.length; i++) {
      if (containers[i].angle === undefined) {
        containers[i].angle = 6;
      } else {
        containers[i].angle += 6;
      }
      containers[i].style.webkitTransform = 'rotateZ('+ containers[i].angle +'deg)';
      containers[i].style.transform = 'rotateZ('+ containers[i].angle +'deg)';
    }
  }, 1000);
}

Теперь, когда секундной и минутной стрелкой занимается JavaScript, обновим CSS, заменив свойства animation на transition.

.minutes-container {
  transition: transform 0.3s cubic-bezier(.4,2.08,.55,.44);
}
.seconds-container {
  transition: transform 0.2s cubic-bezier(.4,2.08,.55,.44);
}

Эти переходы будут применяться к свойству transform и в них используется временная функция cubic-bezier (кубическая-Безье). Эта временная функция добавляет стрелке  небольшой отскок.

Часы в стиле iOS 7

Я большой поклонник минимализма часов, использованных в iOS 7 у Apple. С тех пор они увеличили длину стрелок, но мне нравится более короткая версия.

Задав стиль стрелкам и добавив фон с цифрами, мы можем с легкостью воссоздать этот образ.

Сам по себе этот дизайн является эволюцией Швейцарских железнодорожных часов Ганса Хильфикера. Немного изменив стиль и добавив новое фоновое изображение, мы можем адаптировать наши часы под этот стиль.

Если вы придумаете другие варианты, дайте мне знать.

Использование Moment.js

Одной из идей, появившихся, когда я решил написать этот пост, было воссоздание образа часов в отеле, когда трое часов показывают разные временные пояса.

Самым простым способом настроить часы на разные временные пояса является использование великолепной библиотеки Moment.js Timezone .

Работу этого кода вы можете увидеть на Codepen.

Поддержка браузерами

Современные браузеры умеют работать с использованными в этой статье CSS-переходами и анимациями. IE10+, последние версии Chrome и Firefox поддерживают их без префиксов, а Safari требует префикса -webkit .

Не забывайте использовать свойства с префиксами и в JavaScript.

P.S. Это тоже может быть интересно:

7 Комментарии

  1. v1vendi

    Тема классная, но просто при пользовании setInterval со временем будет рассинхрон настоящего времени и интервала. поэтому время каждую секунду надо устанавливать не инкрементально, а сверяясь с временем системы

  2. kirbox

    На всех часах переход минутной стрелки происходит на 46-й секунде.

  3. Василий

    Добрый день!
    как можно вставить на сайт ваши часики подскажите пожалуйста!
    мне нужно без цифирблата только стрелки,да и возможно будет фон свой устанавливать?

    1. Василий

      ответьте по почте!

      1. Максим Усачев

        Добрый день! Постараюсь сегодня-завтра ответить вам. Оставьте вашу почту пожалуйста.

        1. Василий

          waska9201@yandex.ru За ранее Спасибо!

  4. Санька

    <abbr title="это норма"

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.