Разработка функционала обратного отсчёта (таймер акции) 1С-Битрикс
Акция без дедлайна — это не акция, а постоянная скидка. Таймер обратного отсчёта создаёт ощущение ограниченности предложения и подталкивает к решению. Но в Битрикс нет штатного компонента обратного отсчёта. Стандартный механизм скидок умеет задавать период действия, а вот визуальный таймер на фронте — задача для кастомной разработки. Разберём, как связать таймер с реальными правилами скидок, чтобы дата окончания бралась из базы, а не была захардкожена в шаблоне.
Источник данных: откуда брать дату окончания
Таймер должен показывать реальный срок акции, а не декоративные цифры. В Битрикс даты акций хранятся в нескольких местах:
Скидки каталога — таблица b_catalog_discount, поля ACTIVE_FROM и ACTIVE_TO. Получаем через \Bitrix\Catalog\DiscountTable::getList() с фильтром ACTIVE = Y и ACTIVE_TO > NOW().
Правила корзины — таблица b_sale_discount, аналогичные поля. API: \Bitrix\Sale\Internals\DiscountTable::getList().
Свойство товара — можно создать свойство инфоблока PROMO_END_DATE типа «Дата/Время» и заполнять его вручную или автоматически при привязке товара к акции.
Выбор источника зависит от сценария. Если таймер показывается на карточке товара — удобнее свойство товара или скидка каталога. Если таймер глобальный (баннер на главной) — правило корзины или отдельный инфоблок акций.
Архитектура компонента
Создаём кастомный компонент local:sale.countdown в /local/components/local/sale.countdown/. Структура стандартная:
-
class.php— логика выборки данных. -
templates/.default/template.php— HTML-разметка. -
templates/.default/script.js— JavaScript-логика обратного отсчёта. -
.parameters.php— настраиваемые параметры компонента.
Параметры компонента:
| Параметр | Тип | Описание |
|---|---|---|
| SOURCE_TYPE | list | Источник: catalog_discount, sale_discount, iblock_property |
| DISCOUNT_ID | int | ID скидки (для catalog/sale) |
| IBLOCK_ID | int | ID инфоблока (для свойства товара) |
| ELEMENT_ID | int | ID элемента (для свойства товара) |
| PROPERTY_CODE | string | Код свойства с датой окончания |
| DISPLAY_FORMAT | list | Формат: дни+часы+минуты+секунды или часы+минуты+секунды |
| ACTION_ON_EXPIRE | list | Действие по истечении: скрыть / показать сообщение |
| CACHE_TIME | int | Время кэширования |
В class.php компонент получает дату окончания из выбранного источника и передаёт в шаблон timestamp:
$this->arResult['TIMESTAMP_END'] = (new \Bitrix\Main\Type\DateTime($endDate))->getTimestamp();
JavaScript: клиентский отсчёт
Таймер работает целиком на клиенте. Серверная часть отдаёт только timestamp окончания. Это принципиально: AJAX-запросы каждую секунду — нагрузка без смысла.
Ключевой момент — синхронизация времени. Клиентские часы могут отличаться от серверных. Решение: сервер передаёт не только TIMESTAMP_END, но и TIMESTAMP_SERVER — текущее серверное время. JavaScript вычисляет дельту и корректирует отсчёт:
const serverNow = parseInt(container.dataset.serverTime);
const clientNow = Math.floor(Date.now() / 1000);
const drift = serverNow - clientNow;
const remaining = endTime - (Math.floor(Date.now() / 1000) + drift);
Обновление DOM каждую секунду через setInterval — рабочий вариант. Но для множества таймеров на странице (листинг товаров с акциями) лучше один requestAnimationFrame-цикл, который обновляет все таймеры за один проход.
Действие по истечении. Когда remaining <= 0, таймер должен не просто остановиться. Варианты: скрыть блок акции, заменить кнопку «Купить со скидкой» на обычную, показать сообщение «Акция завершена». Для смены кнопки — AJAX-запрос к серверу для проверки актуальности скидки и перерендер блока цены.
Интеграция с composite-кэшем
Composite cache (автокомпозит) Битрикс кэширует HTML-страницу целиком. Таймер с серверным timestamp в HTML сломает кэш: каждый хит будет уникальным.
Решение — вынести блок таймера в динамическую область. В template.php:
$frame = new \Bitrix\Main\Page\Frame('countdown_' . $this->arParams['DISCOUNT_ID']);
$frame->begin();
// HTML таймера
$frame->end();
Внутри динамической области HTML обновляется при каждом запросе, а остальная страница отдаётся из кэша.
Альтернатива — не выводить timestamp в HTML вообще. Вместо этого хранить его в отдельном endpoint (/ajax/countdown.php), который JavaScript запрашивает один раз при загрузке. Страница кэшируется полностью, данные таймера загружаются отдельно.
Привязка к правилам скидок
Таймер сам по себе — только визуал. Он должен быть синхронизирован с реальным правилом скидки. Если менеджер продлил акцию в админке — таймер должен обновиться автоматически.
Для этого компонент при каждом сбросе кэша заново запрашивает ACTIVE_TO из таблицы скидок. Время кэширования компонента ставим небольшим — 300-600 секунд. Это компромисс между актуальностью и нагрузкой.
Дополнительно: обработчик OnAfterCatalogDiscountUpdate / OnAfterSaleDiscountUpdate для сброса кэша компонента при изменении скидки. Тегированный кэш (\Bitrix\Main\Data\TaggedCache) с тегом catalog_discount_{ID} решает задачу точечно.
Таймер на листинге товаров
Отдельная задача — показать таймеры на странице каталога, где 20-50 товаров. Каждый может иметь свою акцию с отдельным сроком. Компонент вызывается в цикле catalog.section — это множественные SQL-запросы.
Оптимизация: в class.php реализуем batch-режим. Компонент принимает массив ELEMENT_IDS, одним запросом получает все даты и возвращает массив timestamp'ов. В шаблоне catalog.section — один вызов вместо N.
Сроки реализации
| Вариант | Состав | Срок |
|---|---|---|
| Простой таймер | Один компонент, одна скидка, статический endpoint | 3-4 дня |
| Полное решение | Batch-режим, composite-совместимость, синхронизация с правилами, автосброс кэша | 7-10 дней |







