Как создать таймер на сайт с помощью HTML, CSS и JavaScript

В статье рассказывается, как создать таймер на сайт, используя только HTML, CSS и JavaScript. Вот что мы хотим получить:

Основные функции таймера:

  • Отображение оставшегося времени.
  • Преобразование времени в формат MM:SS.
  • Изменение цвета, когда оставшееся время приближается к нулю.
  • Отображение оставшегося времени в виде анимированного кольца.

Пошаговая реализация:

Шаг 1. Начните с базовой разметки и стилей

Мы добавим svg с элементом circle внутри, чтобы нарисовать кольцо таймера. А также добавим интервал, чтобы показать оставшееся значение времени. Для этого мы вставляем JavaScript в HTML и включаем в DOM, указывая элемент #app.

document.getElementById("app").innerHTML = `
<div class="base-timer">
  <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <g class="base-timer__circle">
      <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45" />
    </g>
  </svg>
  <span>
    <!-- Remaining time label -->
  </span>
</div>
`;

Далее используем CSS, чтобы:

  • Установить размер таймера обратного отсчета.
  • Удалить заливку и обводку из элемента круга.
  • Установить ширину и цвет кольца.
/* Устанавливаем высоту и ширину контейнера */
.base-timer {
  position: relative;
  height: 300px;
  width: 300px;
}

/* Удаляем стили SVG, которые могли бы скрыть временную метку */
.base-timer__circle {
  fill: none;
  stroke: none;
}

/* Контур SVG, который отображает прогресс времени */
.base-timer__path-elapsed {
  stroke-width: 7px;
  stroke: grey;
}

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

Шаг 1. Начните с базовой разметки и стилей

Шаг 2. Настройка временной метки

HTML-код содержит пустой элемент <span> для отображения оставшегося время. Мы добавим сюда соответствующее значение в формате MM:SS с помощью метода formatTimeLeft.

function formatTimeLeft(time) {
  // Наибольшее целое число меньше или равно результату деления времени на 60.
  const minutes = Math.floor(time / 60);
  
  // Секунды – это остаток деления времени на 60 (оператор модуля)
  let seconds = time % 60;
  
  // Если значение секунд меньше 10, тогда отображаем его с 0 впереди 
  if (seconds < 10) {
    seconds = `0${seconds}`;
  }

  // Вывод в формате MM:SS
  return `${minutes}:${seconds}`;
}

После этого мы используем только что созданный метод в шаблоне.

document.getElementById("app").innerHTML = `
<div class="base-timer">
  <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <g class="base-timer__circle">
      <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
    </g>
  </svg>
  <span id="base-timer-label" class="base-timer__label">
    ${formatTime(timeLeft)}
  </span>
</div>
`

Чтобы вывести значение внутри кольца, нужно обновить стили.

// Начинаем с исходного значения в 20 секунд
const TIME_LIMIT = 20;

// Изначально осталось полное время интервала, но оно будет отсчитываться 
// и вычитаться из TIME_LIMIT
let timePassed = 0;
let timeLeft = TIME_LIMIT;
Шаг 2. Настройка временной метки

Теперь заставим таймер отсчитывать от 20 до 0.

Шаг 3: Обратный отсчет

У нас есть значение timeLimit, которое представляет собой начальное время. А также значение timePassed, которое указывает, сколько времени прошло с момента начала отсчета.

Увеличим значение timePassed на секунду и пересчитаем timeLeft с помощью функции setInterval. Для этого реализуем метод startTimer, который будет:

  • Устанавливать интервал счетчика.
  • Увеличивать значение timePassed каждую секунду.
  • Пересчитывать значение timeLeft.
  • Обновлять значение метки в шаблоне.

Сохраним ссылку на этот объект интервала в переменной timerInterval, чтобы очистить его при необходимости.

let timerInterval = null;

document.getElementById("app").innerHTML = `...`

function startTimer() {
  timerInterval = setInterval(() => {
    
    // Количество времени, которое прошло, увеличивается на  1
    timePassed = timePassed += 1;
    timeLeft = TIME_LIMIT - timePassed;
    
    // Обновляем метку оставшегося времени
    document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
  }, 1000);
}

У нас есть метод, который запускает таймер обратного отсчета. С его помощью запустим таймер.

document.getElementById("app").innerHTML = `...`
startTimer();

Теперь таймер отсчитывает время. Реализуем изменение цвета временной метки при различных значениях.

Шаг 3: Обратный отсчет

Шаг 4: Перекрываем кольцо таймера другим кольцом

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

Сначала добавим элемент path в SVG.

document.getElementById("app").innerHTML = `
<div class="base-timer">
  <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <g class="base-timer__circle">
      <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
      <path
        id="base-timer-path-remaining"
        stroke-dasharray="283"
        class="base-timer__path-remaining ${remainingPathColor}"
        d="
          M 50, 50
          m -45, 0
          a 45,45 0 1,0 90,0
          a 45,45 0 1,0 -90,0
        "
      ></path>
    </g>
  </svg>
  <span id="base-timer-label" class="base-timer__label">
    ${formatTime(timeLeft)}
  </span>
</div>
`;

После этого добавим несколько стилей, чтобы круговая траектория выглядела как оригинальное серое кольцо. Важно, чтобы свойство stroke-width принимало значение, равное размеру исходного кольца. А также чтобы длительность transition была ​​установлена ​​на 1 секунду.

.base-timer__path-remaining {
  /* Такая же ширина, что и у исходного кольца */
  stroke-width: 7px;

  /* Замыкаем концы линии, чтобы создать круг */
  stroke-linecap: round;

  /* Делаем так, чтобы анимация начиналась вверху */
  transform: rotate(90deg);
  transform-origin: center;

  /* Одна секунда подгоняется под таймер обратного отсчета */
  transition: 1s linear all;

  /* Задаем смену цвета кольца, когда обновляется значение цвета */
  stroke: currentColor;
}

.base-timer__svg {
  /* Переворачиваем кольцо и задаем движение анимации слева направо */
  transform: scaleX(-1);
}

Но кольцо таймера пока не анимируется.

Шаг 4: Перекрываем кольцо таймера другим кольцом

Для анимации линии оставшегося времени мы будем использовать свойство stroke-dasharray.

Шаг 5. Анимация кольца прогресса

Посмотрим, как будет выглядеть кольцо с различными значениями stroke-dasharray.

Шаг 5. Анимация кольца прогресса

Свойство stroke-dasharray делит оставшееся кольцо времени на отрезки равной длины. Это происходит, когда мы задаем stroke-dasharray число от 0 до 9.

Посмотрим, как это свойство будет себя вести, если передать ему два значения: 10 и 30.

Шаг 5. Анимация кольца прогресса - 2

stroke-dasharray: 10 30

Это устанавливает длину первой секции (оставшегося времени) на 10, а второй секции (прошедшего времени) – на 30. Мы можем использовать это в нашем таймере обратного отсчета.

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

Вычислить длину дуги можно по следующей формуле:

Length = 2πr = 2 * π * 45 = 282,6

Это значение используется при первоначальном наложении кольца.

stroke-dasharray: 283 283

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

Создадим метод для подсчета оставшейся доли начального времени. Еще один – для вычисления значения stroke-dasharray и обновление элемента <path>, представляющего оставшееся время.

// Делим оставшееся время на определенный временной лимит
function calculateTimeFraction() {
  return timeLeft / TIME_LIMIT;
}
    
// Обновляем значение свойства dasharray, начиная с 283
function setCircleDasharray() {
  const circleDasharray = `${(
    calculateTimeFraction() * FULL_DASH_ARRAY
  ).toFixed(0)} 283`;
  document
    .getElementById("base-timer-path-remaining")
    .setAttribute("stroke-dasharray", circleDasharray);
}

Также необходимо обновлять контур каждую секунду. Для этого вызовем метод setCircleDasharray внутри timerInterval.

function startTimer() {
  timerInterval = setInterval(() => {
    timePassed = timePassed += 1;
    timeLeft = TIME_LIMIT - timePassed;
    document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
    
    setCircleDasharray();
  }, 1000);
}

Но анимация отстает на 1 секунду. Когда мы достигаем 0, все еще виден кусочек кольца.

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

function calculateTimeFraction() {
  const rawTimeFraction = timeLeft / TIME_LIMIT;
  return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}

Шаг 6: Изменение цвета в определенные моменты времени

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

// Оповещение на 10 секунде
const WARNING_THRESHOLD = 10;
// Предупреждение на 5 секунде
const ALERT_THRESHOLD = 5;

const COLOR_CODES = {
  info: {
    color: "green"
  },
  warning: {
    color: "orange",
    threshold: WARNING_THRESHOLD
  },
  alert: {
    color: "red",
    threshold: ALERT_THRESHOLD
  }
};

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

function setRemainingPathColor(timeLeft) {
  const { alert, warning, info } = COLOR_CODES;

  // Если оставшееся время меньше или равно 5, удаляем класс "warning" и применяем класс "alert".
  if (timeLeft <= alert.threshold) {
    document
      .getElementById("base-timer-path-remaining")
      .classList.remove(warning.color);
    document
      .getElementById("base-timer-path-remaining")
      .classList.add(alert.color);

  // Если оставшееся время меньше или равно 10, удаляем базовый цвет и применяем класс "warning".
  } else if (timeLeft <= warning.threshold) {
    document
      .getElementById("base-timer-path-remaining")
      .classList.remove(info.color);
    document
      .getElementById("base-timer-path-remaining")
      .classList.add(warning.color);
  }
}

Мы удаляем один класс CSS, когда таймер обратного отсчета достигает определенной точки, и добавляем вместо него другой. Объявим эти классы.

.base-timer__path-remaining.green {
  color: rgb(65, 184, 131);
}

.base-timer__path-remaining.orange {
  color: orange;
}

.base-timer__path-remaining.red {
  color: red;
}

Все готово. Ниже приводится полная демо-версия:

Вадим Дворниковавтор-переводчик статьи «How to Create an Animated Countdown Timer With HTML, CSS and JavaScript»