script.js — ванільний JavaScript

script.js — ванільний JavaScript

Основні правила

  • ✅ Тільки vanilla JS — без jQuery, Swiper, Slick, будь-яких CDN бібліотек
  • ✅ IIFE обгортка щоб не забруднювати глобальний scope
  • ✅ Guard від подвійної ініціалізації (if (el.dataset.init) return)
  • ✅ Відносна навігація через el.querySelector(), не document.getElementById()
  • ❌ Не використовувати id-атрибути для пошуку — секція може бути кілька разів на сторінці

Базовий паттерн

(function () {
  document.querySelectorAll('.cms-section-countdown').forEach(function (el) {
    if (el.dataset.init) return;   // guard: ще раз не ініціалізувати
    el.dataset.init = '1';

    // навігація лише всередині поточного el
    var timerEl  = el.querySelector('.countdown__timer');
    var totalSec = parseInt(el.dataset.seconds || '3600', 10);

    // ... логіка
  });
})();

Countdown timer

(function () {
  document.querySelectorAll('.cms-section-countdown').forEach(function (el) {
    if (el.dataset.init) return;
    el.dataset.init = '1';
    var timerEl  = el.querySelector('.countdown__timer');
    var totalSec = parseInt(el.dataset.seconds || '3600', 10);

    function pad(n) { return n < 10 ? '0' + n : '' + n; }
    function render(s) {
      var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
      timerEl.innerHTML =
        '<span class="js-cd-h">' + pad(h) + '</span>:' +
        '<span class="js-cd-m">' + pad(m) + '</span>:' +
        '<span class="js-cd-s">' + pad(sec) + '</span>';
    }
    render(totalSec);
    setInterval(function () { if (totalSec > 0) { totalSec--; render(totalSec); } }, 1000);
  });
})();

HTML для даних: <div class="countdown__timer" data-seconds="{{TIMER_SECONDS}}"></div>

Vanilla slider (Swiper-сумісна розмітка)

(function () {
  document.querySelectorAll('.cms-section-testimonials .swiper-reviews').forEach(function (swiper) {
    if (swiper.dataset.init) return;
    swiper.dataset.init = '1';
    var wrapper  = swiper.querySelector('.swiper-wrapper');
    var slides   = swiper.querySelectorAll('.swiper-slide');
    var total    = slides.length, current = 0;
    var pagination = swiper.querySelector('.swiper-pagination');
    var bullets  = [];

    for (var i = 0; i < total; i++) {
      var b = document.createElement('span');
      b.className = 'swiper-pagination-bullet' + (i === 0 ? ' swiper-pagination-bullet-active' : '');
      (function(idx) { b.addEventListener('click', function() { go(idx); }); })(i);
      pagination.appendChild(b); bullets.push(b);
    }
    function go(idx) {
      current = (idx + total) % total;
      wrapper.style.transform = 'translateX(-' + (current * 100) + '%)';
      bullets.forEach(function(b, i) {
        b.className = 'swiper-pagination-bullet' + (i === current ? ' swiper-pagination-bullet-active' : '');
      });
    }
    var nextBtn = swiper.querySelector('.swiper-button-next');
    var prevBtn = swiper.querySelector('.swiper-button-prev');
    if (nextBtn) nextBtn.addEventListener('click', function() { go(current + 1); });
    if (prevBtn) prevBtn.addEventListener('click', function() { go(current - 1); });
    // Touch support
    var startX = 0;
    wrapper.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; }, {passive:true});
    wrapper.addEventListener('touchend', function(e) {
      if (Math.abs(e.changedTouches[0].clientX - startX) > 40)
        go(e.changedTouches[0].clientX < startX ? current + 1 : current - 1);
    });
  });
})();

Маска телефону (Україна +380)

(function () {
  document.querySelectorAll('.cms-section-order-form input[name="phone"]').forEach(function(input) {
    if (input.dataset.masked) return;
    input.dataset.masked = '1';
    input.addEventListener('input', function() {
      var raw = this.value.replace(/\D/g, '');
      if (raw.startsWith('380')) raw = raw.slice(3);
      else if (raw.startsWith('0')) raw = raw.slice(1);
      raw = raw.slice(0, 9);
      var out = '+380';
      if (raw.length > 0) out += ' ' + raw.slice(0, 2);
      if (raw.length > 2) out += ' ' + raw.slice(2, 5);
      if (raw.length > 5) out += '-' + raw.slice(5, 7);
      if (raw.length > 7) out += '-' + raw.slice(7, 9);
      this.value = out;
    });
    input.addEventListener('focus', function() { if (!this.value) this.value = '+380 '; });
  });
})();

Відправка форми в Telegram

(function () {
  document.querySelectorAll('.cms-section-order-form .m1-form').forEach(function(form) {
    if (form.dataset.init) return;
    form.dataset.init = '1';
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      var token  = this.dataset.tgToken || '';
      var chatId = this.dataset.tgChat  || '';
      var data   = Object.fromEntries(new FormData(this));
      var text   = Object.entries(data)
        .filter(([k]) => !k.startsWith('_'))
        .map(([k, v]) => '*' + k + ':* ' + v).join('\n');
      fetch('https://api.telegram.org/bot' + token + '/sendMessage', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ chat_id: chatId, text: text, parse_mode: 'Markdown' })
      }).then(function() {
        form.style.display = 'none';
        form.closest('.cms-section-order-form').querySelector('.form-success').style.display = 'block';
      });
    });
  });
})();
Оновлено: 26.10.2026 22:43 👁 22