ФронтендJavaScriptHTML / CSS

Таймер обратного отсчёта на JavaScript: дни, часы и точный ноль

Создаём таймер обратного отсчёта на JavaScript: считаем время от дедлайна, форматируем дни и часы, не накапливаем ошибку setInterval и останавливаемся на нуле.

Кодик

Автор

5 мин чтения

Точный таймер каждый раз считает разницу между Date.now() и дедлайном. Он не вычитает секунду вслепую, поэтому смена вкладки или задержка браузера не накопит большую ошибку.

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

1Фиксируем

Храним timestamp дедлайна, а не число оставшихся тиков.

2Разбираем

Переводим миллисекунды в дни, часы, минуты и секунды.

3Останавливаем

На нуле очищаем интервал и показываем завершение.

Как хранить дедлайн и остаток

В HTML достаточно четырёх значений и подписи. JavaScript получает deadline один раз, а функция render на каждом вызове заново вычисляет remaining. Math.max не даёт уйти в отрицательные числа.

ЗначениеДелительПосле деления
Дни86 400 000floor
Часы3 600 000остаток по 24
Минуты60 000остаток по 60
Секунды1 000остаток по 60

После этого шага у каждой части есть одна роль. Интерфейс собирает действие, логика меняет состояние, а вывод показывает признак успеха. Это проще отлаживать, чем один большой обработчик.

Один тик таймера
Время каждый раз считается заново. На нуле interval очищается

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

Пишем расчёт и обновление интерфейса

В примере дедлайн задан на 26 часов вперёд, чтобы были видны и дни, и часы. В реальном проекте передавайте ISO-дату с часовым поясом. format с padStart сохраняет две цифры, а interval очищается ровно один раз.

const deadline = Date.now() + 26 * 60 * 60 * 1000;
const output = document.querySelector('[data-countdown]');
const pad = value => String(value).padStart(2, '0');

function getParts(milliseconds) {
  const total = Math.max(0, Math.floor(milliseconds / 1000));
  return {
    days: Math.floor(total / 86400),
    hours: Math.floor(total / 3600) % 24,
    minutes: Math.floor(total / 60) % 60,
    seconds: total % 60,
  };
}

function render() {
  const remaining = deadline - Date.now();
  const time = getParts(remaining);
  output.textContent = time.days + ' дн. ' + pad(time.hours) + ':' + pad(time.minutes) + ':' + pad(time.seconds);
  if (remaining <= 0) {
    clearInterval(interval);
    output.textContent = 'Время вышло';
  }
}

render();
const interval = setInterval(render, 250);
Что должно работать после первого запуска

Дедлайн хранится абсолютным timestamp. Интерфейс обновляется сразу, без первой пустой секунды. Пауза фоновой вкладки не меняет итоговый момент. На нуле интервал очищается, отрицательных чисел нет.

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

Почему setInterval не является секундомером

Интервал 250 миллисекунд не делает таймер точнее как часы. Он лишь быстрее замечает переход на новую отображаемую секунду. Точность даёт повторный расчёт deadline - Date.now. Если вкладка была заморожена минуту, следующий render сразу покажет актуальный остаток.

ПодходПосле паузы вкладкиОшибка
seconds--Продолжает со старого числаНакапливается
deadline - Date.now()Прыгает к актуальному времениНе накапливается
requestAnimationFrameОбычно заморожен в фонеНе нужен для секунд
Web WorkerСложнее архитектураИзбыточен для виджета

Сохраняйте в одном месте то, что может измениться. Если одно и то же значение скопировано в три функции, после первой правки они начнут расходиться. Один объект состояния и одна функция отрисовки снимают эту проблему.

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

Практика: проверяем паузу и ноль

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

Проверка своими руками
  1. Поставьте дедлайн на 10 секунд.
  2. Откройте другую вкладку на половину времени.
  3. Вернитесь и сравните остаток с часами.
  4. Задайте дедлайн в прошлом.
  5. Проверьте формат 01:05:09.
  6. Добавьте aria-live="polite" только итоговой подписи, не всему секундному счётчику.
Готово, если выполняются все пункты

После фоновой паузы время остаётся верным. Часы и минуты всегда двузначные. Дедлайн в прошлом сразу показывает завершение. Интервал очищается на нуле.

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

Доводим таймер до готового виджета

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

Каждый тик делает seconds--

Задержки браузера накапливают ошибку.

Нет первого render

Экран пустует до первого интервала.

Время уходит ниже нуля

Ограничьте Math.max и остановите interval.

Дата без часового пояса

У пользователей получится разный момент.

Сверьтесь с первичным источником. Поведение используемых функций и ограничения примера проверяйте по документацией MDN по setInterval. Документация особенно важна, когда меняются версии, политика платформы или формат ответа API.

Что получится в итоге

Готовый результат проекта: Таймер обратного отсчёта на JavaScript: дни, часы и точный ноль
Готовый виджет показывает день, часы, минуты и секунды, переживает паузу вкладки и заканчивает ровно на нуле.

Короткие ответы
Почему нельзя просто уменьшать число?

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

Что является источником истины?

Абсолютный timestamp дедлайна.

Зачем обновлять чаще секунды?

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

Как понять, что проект готов?

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

Можно ли копировать код целиком?

Можно как стартовую точку, но сначала замените значения из примера, затем запустите проект по шагам. Если вы не можете объяснить одну строку, её лучше разобрать до следующего шага.

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

Даты, функции и DOM можно отработать в курсе JavaScript. Подключение файла проверьте по короткой инструкции.

Если нужен интерфейс события, соедините таймер с модальным окном или сохраните настройки по примеру Todo-листа. В Кодике удобно тестировать короткие дедлайны и крайние значения.