Как создать таймер на сайт с помощью 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;
}
В результате мы получили шаблон, который выглядит следующим образом.

Шаг 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;

Теперь заставим таймер отсчитывать от 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();
Теперь таймер отсчитывает время. Реализуем изменение цвета временной метки при различных значениях.

Шаг 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);
}
Но кольцо таймера пока не анимируется.

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

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

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;
}
Все готово. Ниже приводится полная демо-версия: