Розробка фільтрації за діапазоном цін 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка фільтрації за діапазоном цін 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка фільтрації за діапазоном цін 1С-Бітрікс

Стандартний розумний фільтр 1С-Бітрікс включає ціновий діапазон у вигляді двох текстових полів «від» і «до». У більшості випадків цього достатньо — поки не з'являється вимога: слайдер з двома повзунками, миттєве оновлення каталогу без перезавантаження сторінки, коректна робота з кількома типами цін і граничними значеннями з реальних даних. Стандартний компонент фільтра не покриває ці сценарії без кастомної розробки.

Як працює ціновий фільтр у Бітрікс

Розумний фільтр формує параметр запиту виду PRICE_1[MIN]=100&PRICE_1[MAX]=5000, де 1 — ID типу ціни. CIBlockElement::GetList приймає ці параметри через arFilter з ключами >=PRICE та <=PRICE. При кількох типах цін фільтрація ведеться за базовою ціною або за ціною для групи поточного користувача.

Отримання мінімальної та максимальної ціни з каталогу для ініціалізації слайдера:

// Отримання граничних значень ціни з розділу каталогу
$priceRange = [];
$res = CPrice::GetList(
    ['PRICE' => 'ASC'],
    [
        'CATALOG_GROUP_ID' => 1,
        'ELEMENT_IBLOCK_ID' => $iblockId,
    ],
    false,
    ['nPageSize' => 1]
);
if ($item = $res->Fetch()) {
    $priceRange['min'] = floatval($item['PRICE']);
}

$res = CPrice::GetList(
    ['PRICE' => 'DESC'],
    [
        'CATALOG_GROUP_ID' => 1,
        'ELEMENT_IBLOCK_ID' => $iblockId,
    ],
    false,
    ['nPageSize' => 1]
);
if ($item = $res->Fetch()) {
    $priceRange['max'] = floatval($item['PRICE']);
}

Ці значення передаються до JavaScript через data-* атрибути або JSON у тезі <script>.

Реалізація слайдера подвійного діапазону

Нативний HTML не підтримує слайдер з двома повзунками. Реалізація через два input[type=range] з CSS-позиціонуванням:

class PriceRangeSlider {
  constructor(container, options) {
    this.container = container;
    this.min = options.min || 0;
    this.max = options.max || 100000;
    this.valueMin = options.valueMin || this.min;
    this.valueMax = options.valueMax || this.max;
    this.onChange = options.onChange || function() {};

    this.render();
    this.bindEvents();
  }

  render() {
    this.container.innerHTML = `
      <div class="price-slider">
        <div class="price-slider__track">
          <div class="price-slider__range" id="sliderRange"></div>
        </div>
        <input type="range" class="price-slider__thumb price-slider__thumb--min"
          min="${this.min}" max="${this.max}" value="${this.valueMin}" step="100">
        <input type="range" class="price-slider__thumb price-slider__thumb--max"
          min="${this.min}" max="${this.max}" value="${this.valueMax}" step="100">
      </div>
      <div class="price-inputs">
        <input type="number" class="price-input price-input--min" value="${this.valueMin}">
        <span>—</span>
        <input type="number" class="price-input price-input--max" value="${this.valueMax}">
      </div>
    `;

    this.thumbMin = this.container.querySelector('.price-slider__thumb--min');
    this.thumbMax = this.container.querySelector('.price-slider__thumb--max');
    this.rangeEl = this.container.querySelector('#sliderRange');
    this.inputMin = this.container.querySelector('.price-input--min');
    this.inputMax = this.container.querySelector('.price-input--max');

    this.updateTrack();
  }

  updateTrack() {
    const percent1 = ((this.valueMin - this.min) / (this.max - this.min)) * 100;
    const percent2 = ((this.valueMax - this.min) / (this.max - this.min)) * 100;
    this.rangeEl.style.left = percent1 + '%';
    this.rangeEl.style.width = (percent2 - percent1) + '%';
  }

  bindEvents() {
    this.thumbMin.addEventListener('input', (e) => {
      const val = Math.min(parseInt(e.target.value), this.valueMax - 100);
      this.valueMin = val;
      e.target.value = val;
      this.inputMin.value = val;
      this.updateTrack();
      this.onChange(this.valueMin, this.valueMax);
    });

    this.thumbMax.addEventListener('input', (e) => {
      const val = Math.max(parseInt(e.target.value), this.valueMin + 100);
      this.valueMax = val;
      e.target.value = val;
      this.inputMax.value = val;
      this.updateTrack();
      this.onChange(this.valueMin, this.valueMax);
    });

    this.inputMin.addEventListener('change', (e) => {
      const val = Math.max(this.min, Math.min(parseInt(e.target.value) || this.min, this.valueMax - 100));
      this.valueMin = val;
      e.target.value = val;
      this.thumbMin.value = val;
      this.updateTrack();
      this.onChange(this.valueMin, this.valueMax);
    });

    this.inputMax.addEventListener('change', (e) => {
      const val = Math.min(this.max, Math.max(parseInt(e.target.value) || this.max, this.valueMin + 100));
      this.valueMax = val;
      e.target.value = val;
      this.thumbMax.value = val;
      this.updateTrack();
      this.onChange(this.valueMin, this.valueMax);
    });
  }
}

AJAX-застосування цінового фільтра

Інтеграція слайдера з AJAX-оновленням каталогу:

// Ініціалізація
const priceData = window.__PRICE_RANGE__ || { min: 0, max: 100000 };
const urlParams = new URLSearchParams(window.location.search);
const currentMin = parseInt(urlParams.get('PRICE_1_MIN')) || priceData.min;
const currentMax = parseInt(urlParams.get('PRICE_1_MAX')) || priceData.max;

const slider = new PriceRangeSlider(
  document.getElementById('price-range-container'),
  {
    min: priceData.min,
    max: priceData.max,
    valueMin: currentMin,
    valueMax: currentMax,
    onChange: debounce((min, max) => applyPriceFilter(min, max), 400),
  }
);

function applyPriceFilter(min, max) {
  const url = new URL(window.location.href);

  if (min > priceData.min) {
    url.searchParams.set('PRICE_1_MIN', min);
  } else {
    url.searchParams.delete('PRICE_1_MIN');
  }

  if (max < priceData.max) {
    url.searchParams.set('PRICE_1_MAX', max);
  } else {
    url.searchParams.delete('PRICE_1_MAX');
  }

  // Скидання пагінації при зміні фільтра
  url.searchParams.delete('PAGEN_1');

  loadCatalog(url.toString());
}

function loadCatalog(url) {
  const catalogEl = document.getElementById('catalog-container');
  catalogEl.classList.add('loading');

  fetch(url, {
    headers: { 'X-Requested-With': 'XMLHttpRequest' }
  })
    .then(r => r.text())
    .then(html => {
      const parser = new DOMParser();
      const doc = parser.parseFromString(html, 'text/html');
      const newCatalog = doc.getElementById('catalog-container');
      if (newCatalog) {
        catalogEl.innerHTML = newCatalog.innerHTML;
      }
      catalogEl.classList.remove('loading');
      history.pushState(null, '', url);
    });
}

Серверна обробка параметрів ціни

У компоненті каталогу або шаблоні фільтра — приймання параметрів і передача до CIBlockElement::GetList:

// template.php компонента каталогу
$filterPrice = [];

if (!empty($_REQUEST['PRICE_1_MIN'])) {
    $filterPrice['>=MIN_PRICE'] = floatval($_REQUEST['PRICE_1_MIN']);
}
if (!empty($_REQUEST['PRICE_1_MAX'])) {
    $filterPrice['<=MAX_PRICE'] = floatval($_REQUEST['PRICE_1_MAX']);
}

// Об'єднання з основним фільтром
$arFilter = array_merge($arFilter, $filterPrice);

При роботі через розумний фільтр (компонент bitrix:catalog.smart.filter) параметри обробляються автоматично, якщо слайдер передає значення у стандартних полях форми фільтра.

Фільтрація за кількома типами цін

У B2B-каталогах часто кілька типів цін для різних груп покупців. Слайдер повинен працювати з ціною поточної групи:

// Визначення ID типу ціни для поточного користувача
$userGroupIds = CUser::GetUserGroup($USER->GetID());
$priceTypeId = 1; // базова за замовчуванням

$res = CCatalogGroup::GetList(
    ['ID' => 'ASC'],
    ['BUY' => 'Y'],
);
while ($group = $res->Fetch()) {
    if (array_intersect($userGroupIds, $group['BUY_GROUP_IDS'])) {
        $priceTypeId = $group['ID'];
        break;
    }
}

// Передача ID типу ціни до JS
echo '<script>window.__PRICE_TYPE_ID__ = ' . intval($priceTypeId) . ';</script>';

Кейс: каталог електроніки з широким ціновим діапазоном

Інтернет-магазин побутової техніки мав каталог з діапазоном цін від 300 до 450 000 грн. Стандартні поля введення «від/до» працювали некоректно: користувачі вводили значення вручну, помилялися з нулями. Слайдер з логарифмічною шкалою вирішив проблему — нижній діапазон (300–5 000 грн) займав стільки ж екранного простору, скільки верхній (100 000–450 000 грн).

Логарифмічне перетворення значень слайдера:

function toSliderPosition(value, min, max) {
  return (Math.log(value) - Math.log(min)) / (Math.log(max) - Math.log(min));
}

function fromSliderPosition(position, min, max) {
  return Math.round(Math.exp(
    Math.log(min) + position * (Math.log(max) - Math.log(min))
  ) / 100) * 100; // Округлення до сотень
}

Після впровадження частка користувачів, які застосували ціновий фільтр, зросла з 12% до 29%, конверсія з відфільтрованого каталогу — вища на 18% порівняно з невідфільтрованим.

Відображення кількості товарів

Динамічне оновлення лічильника без перезавантаження каталогу:

// Ajax-метод для отримання кількості
if ($_REQUEST['action'] === 'price_count') {
    $min = floatval($_REQUEST['min']);
    $max = floatval($_REQUEST['max']);

    $res = CIBlockElement::GetList(
        [],
        [
            'IBLOCK_ID' => $iblockId,
            'ACTIVE' => 'Y',
            '>=PRICE' => $min,
            '<=PRICE' => $max,
        ],
        []
    );
    $count = $res->SelectedRowsCount();

    header('Content-Type: application/json');
    echo json_encode(['count' => $count]);
    die();
}

JavaScript запитує лічильник з дебаунсом 600 мс під час руху повзунка і оновлює кнопку «Показати N товарів».

Терміни виконання

Слайдер з AJAX-оновленням і стандартними параметрами одного типу ціни — 2–3 робочих дні. Повна реалізація з логарифмічною шкалою, кількома типами цін, лічильником товарів та синхронізацією з розумним фільтром — 4–6 робочих днів.