Перейти к содержанию
Инструкция

Скрипт обратного отсчёта времени до конца акции

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

Обновлено 22 июля 7 минут чтения

Баннер акции с таймером «до конца распродажи осталось 2 дня 05:14:30» подгоняет к покупке и не требует ни jQuery, ни внешних библиотек. Достаточно одного блока разметки и десятка строк на современном JavaScript. Разберём рабочий вариант и починим типичные ошибки старых сниппетов.

Разметка: где хранить дату окончания

Дату конца акции удобно держать прямо в разметке – в data-атрибуте блока. Формат ISO 8601 с указанием времени и часового пояса разбирается всеми браузерами одинаково, поэтому лучше писать полную дату, а не короткую 2026-05-31:

<div id="deal-countdown" class="countdown"
     data-deadline="2026-05-31T23:59:59+03:00"></div>

Почему это важно: строку "2026-05-31" (только дата) стандарт велит трактовать как полночь UTC, а "2026-05-31 23:59:59" (с пробелом вместо T) старые версии Safari вообще отказывались разбирать и возвращали Invalid Date. Полный ISO-формат с поясом снимает обе проблемы.

Скрипт таймера на чистом JavaScript

Забираем дедлайн из атрибута, раз в секунду считаем разницу с текущим моментом и обновляем текст. Современный синтаксис (const/let, стрелочные функции, шаблонные строки) делает код короче и понятнее старого варианта на var:

const box = document.getElementById('deal-countdown');

if (box) {
  const deadline = new Date(box.dataset.deadline).getTime();

  const plural = (n, forms) => {
    const idx = (n % 100 > 4 && n % 100 < 20)
      ? 2
      : [2, 0, 1, 1, 1, 2][Math.min(n % 10, 5)];
    return forms[idx];
  };

  const pad = (n) => String(n).padStart(2, '0');

  const render = () => {
    const diff = deadline - Date.now();

    if (diff <= 0) {
      box.textContent = 'Акция завершена';
      clearInterval(timer);
      return;
    }

    const totalSec = Math.floor(diff / 1000);
    const days = Math.floor(totalSec / 86400);
    const hours = Math.floor((totalSec % 86400) / 3600);
    const mins = Math.floor((totalSec % 3600) / 60);
    const secs = totalSec % 60;

    box.textContent =
      `${days} ${plural(days, ['день', 'дня', 'дней'])} ` +
      `${pad(hours)}:${pad(mins)}:${pad(secs)}`;
  };

  render();                       // сразу, без задержки в секунду
  const timer = setInterval(render, 1000);
}

Что здесь исправлено по сравнению со старым сниппетом

  • Отрицательные значения. Старый код продолжал считать после окончания акции и показывал минусы. Теперь при diff <= 0 выводится «Акция завершена» и интервал останавливается через clearInterval.
  • Мгновенный старт. Прежний таймер целую секунду висел пустым, пока не сработает первый setInterval. Мы вызываем render() сразу.
  • Округление. parseInt заменён на Math.floor – это семантически правильнее для отбрасывания дробной части и не зависит от строкового приведения.
  • Русские подписи и склонение. Вместо «Days/Hours» – «день/дня/дней» с корректной формой, а часы/минуты/секунды выводятся в привычном формате чч:мм:сс с ведущими нулями (padStart).
  • Проверка наличия блока. if (box) защищает от ошибки на страницах, где баннера нет.

Почему счёт всегда от Date.now()

Каждый тик берёт свежую разницу «дедлайн минус текущий момент», а не вычитает секунду из прошлого значения. Поэтому таймер не накапливает ошибку и остаётся точным, даже если вкладку свернули, а браузер придержал таймеры для экономии батареи – вернувшись, пользователь увидит верное время.

Подводные камни

  • Часовой пояс дедлайна. Указывайте пояс прямо в дате (+03:00). Иначе «полночь» для клиента в Калининграде и во Владивостоке наступит в разные моменты, и акция закончится у всех по-разному.
  • Клиентские часы врут. Таймер опирается на время устройства посетителя. Если критична точность до секунды (аукцион, флеш-распродажа), сверяйтесь с серверным временем, а не только с Date.now().
  • Несколько таймеров. Скрипт написан под один блок по id. Для нескольких акций используйте класс и querySelectorAll(...).forEach(...), читая дедлайн из data-атрибута каждого.
  • Кэш страницы. Дедлайн в разметке кэшируется вместе со страницей – это нормально: сам отсчёт живой, потому что считается на клиенте. Менять дату акции нужно в исходнике, а не рассчитывать её на сервере при каждом заходе.

Вариант этого таймера, где дата берётся из свойства элемента инфоблока, – в гайде «Счётчик обратного отсчёта в списке новостей». Про работу с датами на стороне сервера – «Сменить формат даты в CMS Битрикс», «Формат Ydiff» и «Вывод текущего года на PHP».

Частые вопросы

Нужна ли библиотека для таймера обратного отсчёта?

Нет. Таймер до конца акции пишется на чистом JavaScript в десяток строк с setInterval и Date.now(). jQuery и специальные плагины для этого не нужны.

В каком формате указывать дату окончания акции?

Используйте полный ISO 8601 с часовым поясом, например 2026-05-31T23:59:59+03:00. Короткая дата «2026-05-31» трактуется как полночь UTC, а формат с пробелом раньше ломался в Safari.

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

Не остановлен интервал. Когда разница deadline − Date.now() становится

Почему таймер сбивается после сворачивания вкладки?

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

Как поставить несколько таймеров на одной странице?

Задайте блокам общий класс и дедлайн в data-атрибуте, затем переберите их через querySelectorAll(...).forEach(...), создав для каждого свой отсчёт.

Валентина Меланина

Хотите обсудить свой проект?

Помогу с разработкой, аналитикой и AI-видимостью вашего сайта

Если у вас есть задача — от внедрения разметки и аналитики до полной переработки сайта — напишите, обсудим объём и подход.