Таймер обратного отсчёта нужен для акций, запусков, вебинаров и ограниченных предложений. Сделать его можно на чистом JavaScript без библиотек. Главное правило: не уменьшать счётчик на секунду каждую секунду, а каждый раз вычислять оставшееся время от текущих часов. Тогда таймер не отстаёт, даже если вкладка была в фоне или браузер притормозил.
Разметка
<div class="countdown" data-deadline="2099-12-31T23:59:59+06:00">
<span data-unit="days">0</span> дн.
<span data-unit="hours">00</span> ч
<span data-unit="minutes">00</span> мин
<span data-unit="seconds">00</span> с
</div>
Дата окончания задаётся в атрибуте data-deadline в формате ISO: ГГГГ-ММ-ДДTчч:мм:сс и смещение часового пояса. В примере стоит условная дата - подставьте свою. Смещение важно: без него дата будет считаться по часовому поясу посетителя, и у пользователей в разных городах акция закончится в разное время. Со смещением (в примере +06:00, то есть UTC+6) отсчёт идёт до одного и того же момента для всех посетителей.
Скрипт
function startCountdown(element, deadline, onFinish) {
const parts = {
days: element.querySelector('[data-unit="days"]'),
hours: element.querySelector('[data-unit="hours"]'),
minutes: element.querySelector('[data-unit="minutes"]'),
seconds: element.querySelector('[data-unit="seconds"]'),
};
function render() {
const left = Math.max(0, deadline - Date.now());
const total = Math.floor(left / 1000);
parts.days.textContent = Math.floor(total / 86400);
parts.hours.textContent = String(Math.floor(total % 86400 / 3600)).padStart(2, '0');
parts.minutes.textContent = String(Math.floor(total % 3600 / 60)).padStart(2, '0');
parts.seconds.textContent = String(total % 60).padStart(2, '0');
if (left === 0) {
clearInterval(timer);
if (onFinish) onFinish(element);
}
}
const timer = setInterval(render, 1000);
render();
return timer;
}
document.querySelectorAll('.countdown').forEach(element => {
const deadline = new Date(element.dataset.deadline).getTime();
startCountdown(element, deadline, el => {
el.textContent = 'Акция завершена';
});
});
Как это работает:
deadline - Date.now()- сколько миллисекунд осталось. Значение вычисляется заново при каждом обновлении, поэтому ошибка не накапливается;Math.max(0, ...)не даёт уйти в отрицательные числа, если страница открыта уже после окончания;padStart(2, '0')добавляет ведущий ноль:05вместо5;render()вызывается сразу, чтобы цифры появились без задержки в секунду;- по окончании интервал останавливается, и вызывается функция
onFinish, где можно скрыть кнопку, показать сообщение или перезагрузить блок.
Код обрабатывает все элементы с классом countdown, поэтому на странице может быть сколько угодно таймеров с разными датами.
Таймер на N минут
Если таймер должен отсчитывать фиксированный интервал от открытия страницы, например 15 минут на оформление заказа:
const element = document.querySelector('.countdown');
const deadline = Date.now() + 15 * 60 * 1000;
startCountdown(element, deadline);
Если блок дней не нужен, просто уберите его из разметки и соответствующую строку из render().
Сохранение после перезагрузки

В варианте выше таймер начинается заново при каждом обновлении страницы. Чтобы отсчёт продолжался, сохраните время окончания в localStorage:
function getPersistentDeadline(key, minutes) {
const saved = Number(localStorage.getItem(key));
if (saved && saved > Date.now()) {
return saved;
}
const deadline = Date.now() + minutes * 60 * 1000;
localStorage.setItem(key, deadline);
return deadline;
}
const element = document.querySelector('.countdown');
startCountdown(element, getPersistentDeadline('order-timer', 15));
Когда время истечёт, при следующем открытии страницы начнётся новый отсчёт. Если повторный запуск не нужен, проверяйте наличие сохранённого значения отдельно и показывайте сообщение об окончании.
Учтите, что localStorage хранится в браузере посетителя и легко очищается. Если срок действительно важен (например, срок действия скидки), его нужно проверять на сервере, а таймер на странице - только для наглядности.
Время сервера вместо часов посетителя
Date.now() берёт время с компьютера посетителя, а часы на нём могут спешить или отставать. Для точного отсчёта получите время с сервера и вычислите поправку:
const response = await fetch('/api/time'); // сервер возвращает { now: <миллисекунды> }
const { now } = await response.json();
const offset = now - Date.now();
и в render() используйте Date.now() + offset вместо Date.now().
Склонение слов
Если нужно писать «2 дня», «5 дней» вместо сокращений, понадобится функция склонения:
function plural(n, one, few, many) {
const mod10 = n % 10, mod100 = n % 100;
if (mod10 === 1 && mod100 !== 11) return one;
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return few;
return many;
}
plural(21, 'день', 'дня', 'дней'); // 'день'
Для этого можно использовать и встроенный Intl.PluralRules('ru'): он возвращает категорию one, few или many, по которой удобно выбрать нужную форму.
Доступность
Чтобы программы экранного доступа не зачитывали каждую секунду, не ставьте на таймер aria-live. Если важно сообщить о завершении, выведите сообщение в отдельный элемент с aria-live="polite".





Комментарии
Комментариев пока нет - будьте первым.