Часы попробую сделать по статье cssanimation планировала перевести, но получилась смесь перевода и собственных заметок.

Работающий пример на JSFiddle

Использовать будем HTML, CSS, SVG-фоны и немного JS. Мы воспользуемся CSS animation или transitions для всех перемещений, и JavaScript чтобы установить начальное время.

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 анимацией, нам нужны элементы-контейнеры если мы хотим одновременно и позиционировать стрелки и добавлять анимацию.

Здесь у меня возник вопрос корректно ли использовать тэг <article> для такой чисто декоративной задачи. Ни о какой статье и тексте речь не идет, но если взять определение на сайте htmlacademy «цельный, законченный и самостоятельный фрагмент информации», то оспорить сложно. Не текст, но самостоятельный фрагмент информации о времени. Если добавить требование, что такой фрагмент должен безболезненно переноситься на другие страницы, то часы — это article.

Циферблат

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

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

.clock { 
  border-radius: 50%; 
  background: #fff url(/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; 
}

Получится примерно так:

Получим так после удаления класса ‘simple’ ведь в HTML мы его еще не использовали.
Ожидаемый результат из статьи.

Для себя я решила эту проблему добавив циферблат как блок в часы:

<article class="clock"> 
  <div class="clock-face"> 
    <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> 
  </div> 
</article>

т.е. класс clock теперь отвечает за контейнер, а не за циферблат.

Соответственно изменились и стили. Добавился новый класс clock, а старый я переименовала в clock-face т.к. фактически он отвечает за стилизацию циферблата:

.clock{
  width: 20em;
  height: 20em;
  background-color: #e5e5e5;
  border-radius: 2em;
  padding: 1em;
}
.clock-face { 
  border-radius: 50%; 
  background: #fff url(./img/ios_clock.svg) no-repeat center;
  background-size: 88%; 
  height: 20em; 
  /* padding-bottom: 31%; */ 
  position: relative; 
  width: 20em; 
} 
.clock-face:after { 
  background: #000; 
  border-radius: 50%; 
  content: ""; 
  position: absolute; 
  left: 50%; { 
  top: 50%; 
  transform: translate(-50%, -50%); 
  width: 5%; 
  height: 5%; 
  z-index: 10; 
}

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

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

Контейнеры всех стрелок выставляем в верхний левый угол. Сейчас они совпадают с циферблатом.

Теперь можно рисовать стрелки

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

Каждую стрелку изначально выставляем на 12 часов. Так будет проще поворачивать стрелки. Все одинаково. Но начнем с часовой:

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

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

Использование процентов позволит хорошо масштабировать часы в будущем, transform-origin подготавливает стрелку к вращению.

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

Точно аналогично. Но традиционно она тоньше и длиннее

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

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

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

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

Анимация

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

У некоторых часов стрелки идут равномерно, у других прыгают по значениям. Для начала сделаем равномерное движение, а позже добавим и дискретное.

Мы можем использовать один  keyframe для движения всех стрелок по кругу.

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

Мы просто говорим поворачивать элемент к которому применена анимация по кругу. Используя линейную функцию для анимации мы получим равномерное движение. Анимацию добавим для контейнеров.

Т.е. поворачивать будем контейнер, а стрелка в нем останется неподвижна такое решение снимает с нас обязанность думать об оси поворота т.к. центр контейнера как раз совпадает с центром циферблата.

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

Одинаковая анимация, но с разной скоростью (за минуту, час и 12 часов). Очень удобное применение @keyframes. Плюс анимация умеет просто сделать цикл для действия. для transition это сделать сложнее.

Если быть очень внимательным можно заметить что минутная стрелка тоже непрерывно движется

Шаги стрелки

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

Для минутной и секундной стрелок нужно изменить временную функцию linear на steps(60) тогда анимация выполнится не плавно, а за 60 равных шагов. Это нам и нужно! Часовую стрелку изменять не будем т.к. она как правило движется равномерно и по ее положению можно определить примерное время даже без минутной

Устанавливаем время

Часы получились красивые, но не точные. Считают время от обновления страницы. Чтобы установить на часах время нам нужен JavaScript

Код я немного подправила. Во первых заменила var на привычное let. Для понятности добавила hours % 12 для перевода в 12 часовой формат. Браузеру все равно, а вот мне не удобно при отладке углы больше 360 градусов.

Расчет угла для часовой стрелки прост: каждый час 30 градусов (360/12) плюс отклонение из-за минут 30 градусов часовая стрелка проходит за час. Значит должна смещаться на один градус за две минуты.

Минуты и секунды просто каждая по 6 градусов (360/60)

/*
  * Starts any clocks using the user's local time
  * From: cssanimation.rocks/clocks
  */
function initLocalClocks() {
    // Get the local time using JS
    let date = new Date;
    let seconds = date.getSeconds();
    let minutes = date.getMinutes();
    let hours = date.getHours();
    
    // Create an object with each hand and it's angle in degrees
    let hands = [
      {
        hand: 'hours',
        angle: (hours % 12 * 30) + (minutes / 2)
      },
      {
        hand: 'minutes',
        angle: (minutes * 6)
      },
      {
        hand: 'seconds',
        angle: (seconds * 6)
      }
    ];
    // Loop through each of these hands to set their angle
    
    for (let i = 0; i < hands.length; i++) {
      let selector='.' + hands[i].hand;
      let elements = document.querySelectorAll(selector);
            
      for (let j = 0; j < elements.length; j++) {
          elements[j].style.webkitTransform = 'rotateZ('+ hands[i].angle +'deg)';
          elements[j].style.transform = 'rotateZ('+ hands[i].angle +'deg)';
      }
    }
  }

  initLocalClocks()

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

Сначала нам нужно запомнить разницу между показаниями секундной стрелки и началом цикла у минутной:

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

Для этого в циклах выше минутной стрелке (точнее ее контейнеру) добавим атрибут, который будет хранить это смещение. Мы используем то, что в массиве у нас стрелки расположены по убыванию и если i-тая — минутная, то i+1 секундная. Эта часть решения мне не очень нравится, т.к. возможно объект вместо массива повысил бы читаемость кода. Но пока не меняю.

Дальше все просто: задерживаем первый раз движение стрелки на вычисленное отставание и запускаем ход стрелки. Минус — теперь минутную стрелку крутит JS.

function setUpMinuteHands() {
    // Find out how far into the minute we are
    let containers = document.querySelectorAll('.minutes-container');
    let secondAngle = containers[0].getAttribute("data-second-angle");
    if (secondAngle > 0) {
      // Set a timeout until the end of the current minute, to move the hand
      let delay = (((360 - secondAngle) / 6) + 0.1) * 1000;
      setTimeout(function() {
        moveMinuteHands(containers);
      }, delay);
    }
  }
  
  /*
   * Do the first minute's rotation
   */
  function moveMinuteHands(containers) {
    for (let i = 0; i < containers.length; i++) {
      containers[i].style.webkitTransform = 'rotateZ(6deg)';
      containers[i].style.transform = 'rotateZ(6deg)';
    }
    // Then continue with a 60 second interval
    setInterval(function() {
      for (let 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);
  }

Можно исправить это добавлением класса анимации после первого движения, а можно как предлагает автор воспользоваться сложившейся ситуацией и перевести секундную стрелку тоже под ответственность JS. Заодно заменив анимацию на transition можно получить более естественное движение стрелок:

function moveSecondHands() {
    let containers = document.querySelectorAll('.seconds-container');
    setInterval(function() {
      for (let 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);
  }

Transiton позволяет сделать очень красивые переходы, при наличии понимания как переход должен происходить. Получается, что мы жертвуем единообразием ради красоты)

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

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

function initLocalClocks() {
    // Get the local time using JS
    let date = new Date;
    let seconds = date.getSeconds();
    let minutes = date.getMinutes();
    let hours = date.getHours();
    
    // Create an object with each hand and it's angle in degrees
    let hands = {
      hours:{
        angle: (hours * 30) + (minutes / 2)
      },
      minutes:{
        angle: (minutes * 6)
      },
      seconds:{
        angle: (seconds * 6)
      }
    };
    // Loop through each of these hands to set their angle
    
    for (let hand in hands) {
      console.log(hand)
      let selector='.' + hand;
      let elements = document.querySelectorAll(selector);
            
      for (let j = 0; j < elements.length; j++) {
          elements[j].style.webkitTransform = 'rotateZ('+ hands[hand].angle +'deg)';
          elements[j].style.transform = 'rotateZ('+ hands[hand].angle +'deg)';
          // If this is a minute hand, note the seconds position (to calculate minute position later)
          if (hand === 'minutes') {
            elements[j].parentNode.setAttribute('data-second-angle', hands['seconds'].angle);
          }
      }
    }
  }

Текст получается длинный. Moment TZ оставлю на будущее)