Перейти к содержанию

Скрипты для лендинга: таймер, страна по IP, скролл и дата

Готовые скрипты для лендинга на чистом JS: таймер до даты, страна по IP для формы, плавный скролл к форме и текущая дата. Проверено в Chromium, без jQuery…

Скрипты для лендинга: таймер, страна по IP, скролл и дата

Скрипты для лендинга, которые нужны почти в каждой арбитражной связке, умещаются в 20-40 строк: таймер до конца акции, определение страны по IP для скрытого поля формы, плавный скролл к форме и вывод текущей даты. Ниже все четыре на чистом JavaScript, без jQuery и сторонних библиотек. Код вставляется перед закрывающим тегом </body> обычного HTML-лендинга или локального лендинга Keitaro.

Обновлено 1 октября 2026. В первой версии статьи (2024 год) скрипты работали только в удачных условиях. Скролл не запускался без jQuery, таймер после нуля начинал заново с пяти минут, а дата выводилась через document.write и могла стереть страницу. Мы прогнали старый и новый код в Chromium 141 через Playwright. Что было не так, показано в конце каждого раздела.

СкриптЗачем на лендингеЧто исправлено
Таймер обратного отсчётаПоказать реальный срок акцииСчитает до настоящей даты, не обнуляется, не отстаёт в фоновой вкладке
Страна по IPЗаполнить поле country для API партнёркиСначала макрос Keitaro, потом бесплатный IPinfo Lite, заполняются все формы
Скролл к формеДовести до формы по кнопке «Заказать»CSS вместо jQuery, другие ссылки и свайпы работают
Текущая датаДата доставки, год в подвалеБез document.write, формат под язык страницы
Таблица прокручивается вбок

Таймер обратного отсчёта для сайта до даты

Таймер над формой работает, когда за ним стоит настоящий срок: акция до конца месяца, распродажа до воскресенья, набор в поток до определённого числа. Скрипт считает время до даты из атрибута data-deadline, а когда она наступает, заменяет таймер текстом из data-expired.

<div class="countdown" data-deadline="2026-10-31T23:59:59+03:00" data-expired="Акция завершилась">
  До конца акции: <b class="countdown__time">--:--:--</b>
</div>

<script>
(function () {
  const pad = n => String(n).padStart(2, '0');

  document.querySelectorAll('.countdown[data-deadline]').forEach(box => {
    const end = Date.parse(box.dataset.deadline);   // реальная дата окончания акции
    const out = box.querySelector('.countdown__time');
    if (isNaN(end) || !out) return;

    let timer = null;
    function tick() {
      // остаток считаем от часов устройства, а не уменьшаем счётчик:
      // так таймер не отстаёт в фоновой вкладке
      const left = Math.max(0, Math.floor((end - Date.now()) / 1000));
      if (left === 0) {
        box.textContent = box.dataset.expired || '';
        clearInterval(timer);
        return;
      }
      const d = Math.floor(left / 86400);
      const h = Math.floor(left % 86400 / 3600);
      const m = Math.floor(left % 3600 / 60);
      const s = left % 60;
      out.textContent = (d ? d + ' дн. ' : '') + pad(h) + ':' + pad(m) + ':' + pad(s);
    }
    tick();
    if (end > Date.now()) timer = setInterval(tick, 1000);
  });
})();
</script>

Как настроить:

  1. В data-deadline укажите дату окончания акции в формате ГГГГ-ММ-ДДTЧЧ:ММ:СС+03:00. Часовой пояс в конце обязателен. Без него браузер посчитает время по часам посетителя, и в другой стране акция закончится в другое время.
  2. В data-expired напишите, что показать после окончания. Если оставить пустым, блок просто опустеет.
  3. Таймеров на странице может быть несколько, например в первом экране и над формой. Каждый блок с классом countdown считается отдельно.

Остаток каждый раз считается от текущего времени, а не уменьшается на секунду. Это важно: Chrome с 88-й версии проверяет таймеры раз в минуту, если вкладка скрыта дольше 5 минут. Скрипт, который вычитает секунду за тик, в фоне отстаёт и после возвращения показывает неверное время.

Что было не так в старом таймере

Старый скрипт стартовал с 9:59 и, дойдя до нуля, начинал заново с 5:00. В тесте с подменой часов браузера:

  • первые две секунды на экране висело 9:59: первый тик выводил начальное значение и только потом его уменьшал;
  • через 10 минут таймер показывал 4:58, то есть не закончился, а пошёл по второму кругу;
  • когда часы ушли на 5 минут вперёд без срабатывания таймеров (так ведёт себя фоновая вкладка), он сдвинулся на одну секунду, с 9:57 на 9:56;
  • запись window.onload = ... затирает другой обработчик загрузки, если он уже был на странице.

Ниже вывод нашего прогона в Chromium: старый таймер после нуля пошёл на второй круг, а новый после дедлайна показывает «Акция завершилась» и не сбивается, когда вкладка была в фоне.

Вывод теста скриптов для лендинга: старый таймер начинает второй круг, новый показывает Акция завершилась
Наш прогон в Chromium 141: старый таймер после нуля пошёл по второму кругу, старый скролл без jQuery падал с ошибкой

Почему «вечный» таймер опасен

Таймер, который обнуляется после нуля или при перезагрузке, не сообщает никакого срока. В отчёте FTC о тёмных паттернах Bringing Dark Patterns to Light (сентябрь 2022) он описан отдельным пунктом «Baseless Countdown Timer»: фальшивые часы, которые исчезают или начинают заново, когда время вышло. В ЕС ложное заявление, что предложение действует «очень ограниченное время», чтобы подтолкнуть к немедленному решению, входит в список практик, которые директива 2005/29/EC считает недобросовестными при любых обстоятельствах (приложение I, пункт 7). Политика Google Ads «Искажение фактов» распространяется и на целевую страницу: вводящая в заблуждение информация о товаре и предложении там запрещена. Поэтому в data-deadline ставьте только настоящую дату окончания.

Определение страны по IP: скрипт для скрытого поля формы

API партнёрок часто требуют передать страну лида. Если лендинг открывается через Keitaro, отдельный скрипт не нужен: трекер подставляет код страны в макрос {country} (список макросов). Достаточно поля в форме:

<input type="hidden" name="country" value="{country}">

Когда трекера нет или лендинг открыт напрямую, страну можно узнать по IP через бесплатный тариф IPinfo Lite. По документации у него нет дневного и месячного лимита, а в ответе есть двухбуквенный код страны country_code, ASN и континент. Запросы прямо из браузера сервис разрешает. Токен выдают после бесплатной регистрации.

<script>
(function () {
  const TOKEN = 'IPINFO_TOKEN';            // токен IPinfo Lite из личного кабинета
  const FIELD = 'input[name="country"]';   // скрытое поле в форме

  // поле надо заполнять, если в нём нет двухбуквенного кода
  // (пусто или макрос {country} не подставился)
  const isEmpty = el => !/^[A-Z]{2}$/.test(el.value);

  function fill(code) {
    document.querySelectorAll(FIELD).forEach(el => {
      if (isEmpty(el)) el.value = code;
    });
  }

  function run() {
    const fields = [...document.querySelectorAll(FIELD)];
    if (!fields.some(isEmpty)) return;       // все поля уже заполнены
    const known = fields.find(el => !isEmpty(el));
    if (known) return fill(known.value);     // трекер подставил страну в одну из форм

    let saved = null;
    try { saved = sessionStorage.getItem('geo_country'); } catch (e) {}
    if (saved) return fill(saved);

    fetch('https://api.ipinfo.io/lite/me?token=' + TOKEN)
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(data => {
        const code = String(data.country_code || '').toUpperCase();
        if (!/^[A-Z]{2}$/.test(code)) throw new Error('нет country_code');
        try { sessionStorage.setItem('geo_country', code); } catch (e) {}
        fill(code);
      })
      .catch(e => console.warn('Страна не определена:', e.message));
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', run);
  } else {
    run();
  }
})();
</script>

Что делает скрипт:

  • заполняет все поля country на странице, а не только первое: на лендинге обычно две-три формы;
  • не трогает поле, если трекер уже подставил код, и не ходит в API, если код есть хотя бы в одной форме;
  • считает пустым и поле с неподставленным макросом {country};
  • запоминает страну в sessionStorage, при перезагрузке страницы второго запроса нет;
  • при ошибке API пишет предупреждение в консоль и оставляет поле как есть.

Проверка в Chromium: с ответом в формате из документации IPinfo все три формы получили «DE», после перезагрузки запросов к API больше не было. Со значением «PL» от трекера скрипт не сделал ни одного запроса. С неверным токеном API ответил 403, поля остались как были, в консоли появилось предупреждение.

Токен виден в коде страницы. Для бесплатного Lite это допустимо, IPinfo сам приводит пример вызова из браузера. Токен платного тарифа с месячной квотой в клиентский код не ставьте.

Посетитель может отправить форму раньше, чем придёт ответ API. Поэтому в серверном обработчике формы нужна подстраховка. Если сайт за Cloudflare, страна приходит в заголовке CF-IPCountry после включения IP Geolocation в разделе Network (инструкция Cloudflare). Коды XX (страна неизвестна) и T1 (сеть Tor) код ниже отбрасывает. Поле формы нельзя считать подтверждением страны: его меняет посетитель. Заголовку Cloudflare доверяйте только при запросе от доверенного прокси, а при неизвестном гео не выдавайте запасное значение за результат определения IP.

<?php
// Страна из формы, а если её нет, из заголовка Cloudflare
$country = strtoupper(trim($_POST['country'] ?? ''));
if (!preg_match('/^[A-Z]{2}$/', $country)) {
    $country = strtoupper($_SERVER['HTTP_CF_IPCOUNTRY'] ?? '');
}
if (!preg_match('/^[A-Z]{2}$/', $country) || in_array($country, ['XX', 'T1'], true)) {
    $country = 'ГЕО_ОФФЕРА'; // не определили: подставьте ГЕО, под которое льёте
}

Что было не так в старом скрипте

  • Запрос уходил только через секунду после загрузки: скрипт ждал поле через setInterval. В тесте запрос к ipinfo.io стартовал на 1,04 секунды.
  • Заполнялась только первая форма. На странице с двумя формами вторая уходила без страны.
  • С ненастроенным ключом (АПИ_КЛЮЧ) ipinfo.io отвечает 403 «Unknown token», и скрипт молча оставлял поле пустым.

Плавный скролл к форме на CSS и JS

Для кнопки «Заказать», которая ведёт к форме, JavaScript не нужен. Хватает якорной ссылки и двух свойств CSS. scroll-behavior включает плавную прокрутку, а scroll-margin-top оставляет отступ под фиксированной шапкой, чтобы она не закрыла форму.

<style>
  html { scroll-behavior: smooth; }
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
  }
  #order { scroll-margin-top: 80px; } /* высота фиксированной шапки + запас */
</style>

<a href="#order" class="btn">Заказать</a>
...
<form id="order"> ... </form>

Блок prefers-reduced-motion выключает анимацию у тех, кто попросил систему уменьшить движение на экране. Если в шаблоне к форме должны вести кнопки, картинки, название товара и пустые ссылки href="#", добавьте короткий скрипт:

<script>
document.addEventListener('click', function (e) {
  // какие элементы ведут к форме: пустые ссылки "#" и всё с классом to-form
  const link = e.target.closest('a[href="#"], .to-form');
  if (!link) return;
  const form = document.getElementById('order');
  if (!form) return;
  e.preventDefault();
  form.scrollIntoView({ block: 'start' }); // плавность берётся из scroll-behavior в CSS
});
</script>

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

Что было не так в старом скрипте

  • Скрипт написан на jQuery, а подключения библиотеки в примере не было. Без неё в консоли ошибка $ is not defined, и кнопка ничего не делает.
  • С облегчённой сборкой jquery-4.0.0.slim (актуальная версия на jquery.com 4.0.0) другая ошибка: .animate is not a function. В slim-сборке нет модуля анимаций.
  • Класс добавлялся ко всем ссылкам страницы. В тесте ссылка на политику конфиденциальности перестала открываться и тоже прокручивала к форме.
  • Обработчик висел на touchend. Человек, который начал листать страницу пальцем с кнопки, улетал к форме: в тесте одно событие touchend без клика прокрутило страницу до формы.

Текущая дата на JS и год в футере сайта

Дату на лендинге выводят, когда она что-то значит для покупателя: ориентировочная дата доставки или год в подвале, который не нужно менять руками каждый январь. Скрипт заполняет элементы с атрибутом data-date, число в атрибуте сдвигает дату на столько дней вперёд. Формат берётся из языка страницы через Intl.DateTimeFormat, поэтому для лендинга на другое ГЕО достаточно поменять lang у тега <html>.

<p>Закажите сегодня, доставим ориентировочно <span data-date="3" data-format="long"></span>.</p>
<footer>© 2019-<span data-year></span> ООО «Пример»</footer>

<script>
(function () {
  const lang = document.documentElement.lang || 'ru';
  const formats = {
    short: { day: '2-digit', month: '2-digit', year: 'numeric' }, // 01.10.2026
    long:  { day: 'numeric', month: 'long' }                        // 1 октября
  };

  document.querySelectorAll('[data-date]').forEach(el => {
    const d = new Date();
    d.setDate(d.getDate() + (parseInt(el.dataset.date, 10) || 0)); // сдвиг в днях
    const opts = formats[el.dataset.format] || formats.short;
    el.textContent = new Intl.DateTimeFormat(lang, opts).format(d);
  });

  document.querySelectorAll('[data-year]').forEach(el => {
    el.textContent = new Date().getFullYear();
  });
})();
</script>

Проверка в Chromium с датой 1 октября 2026: для lang="ru" скрипт вывел «01.10.2026» и «4 октября», для de «4. Oktober», для es «01/10/2026» и «4 de octubre». 30 декабря сдвиг на три дня дал «2 января», переход через год считается правильно. Дата берётся с устройства посетителя: если часы на телефоне сбиты, дата на странице тоже будет неверной.

Что было не так в старом скрипте

Старый код выводил дату через document.write. В справочнике MDN этот метод помечен как устаревший, а его поведение зависит от того, когда и как подключён скрипт. В тесте:

  • в обычном месте разметки скрипт сработал и вывел «01.10.2026»;
  • вставленный после загрузки страницы (так добавляют код конструкторы и менеджеры тегов) стёр весь лендинг: на экране осталась только дата;
  • подключённый внешним файлом с атрибутом async не вывел ничего, Chrome написал в консоль, что из асинхронного скрипта писать в документ нельзя.

Старая статья предлагала подставлять сегодняшнюю дату как «последний день скидки». Если скидка на самом деле действует каждый день, это та же фальшивая срочность, что и таймер с обнулением. По той же причине не подставляйте скриптом «свежие» даты к отзывам: FTC в 2024 году запретило фальшивые отзывы, в том числе от людей, которых не существует.

Какие скрипты на лендинг больше не ставить

Для формы на сайте уже есть готовые решения: маска ввода телефона с кодом страны, защита формы от дублей лидов и страница «Спасибо за заказ» на 25 языков.

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

Нужен ли jQuery для этих скриптов?

Нет. Все четыре написаны на чистом JavaScript, мы проверяли их в Chromium 141. Если jQuery на лендинге уже подключён, он им не мешает.

Как определить страну пользователя без стороннего API?

Через трекер или CDN. Keitaro подставляет код страны в макрос {country}, Cloudflare передаёт его на сервер в заголовке CF-IPCountry. Запрос к API из браузера нужен, только когда нет ни того, ни другого.

Сколько стоит IPinfo для определения страны?

Тариф IPinfo Lite бесплатный и, по документации, без лимита запросов. Он отдаёт только страну, континент и ASN. Город и признаки VPN и прокси есть в платных тарифах, на 1 октября 2026 года от $26 в месяц при оплате за год.

Можно ли сделать таймер, который сбрасывается при каждом заходе?

Технически можно, но такой таймер не сообщает реального срока. FTC относит его к тёмным паттернам, а директива ЕС о недобросовестных практиках запрещает ложное ограничение по времени. Ставьте дату, когда акция действительно заканчивается.

Почему таймер на сайте отстаёт, если переключиться на другую вкладку?

Браузер реже запускает таймеры в скрытых вкладках: Chrome после 5 минут проверяет их раз в минуту. Скрипт, который вычитает по секунде за тик, при этом отстаёт. Считайте остаток от текущего времени, как в примере выше.

Где взять скрипт кнопки «Назад» для лендинга?

Не ставьте его на страницы с трафиком из Google или Meta: с 15 июня 2026 года это спам по правилам Google и нарушение рекламных политик. Как получить деньги с ушедших посетителей без перехвата истории, смотрите в разборе домонетизации выше по ссылке.

Редакция BoostClicks

Материалы о настройке рекламы, трекинге и автоматизации: инструкции, примеры кода и расчёты с указанием условий проверки.