הפילטר החכם הסטנדרטי ב-1C-Bitrix כולל טווח מחירים כשני שדות טקסט, "מ" ו-"עד". ברוב המקרים, זה מספיק — עד שעולה דרישה: סליידר עם שני ידיות, עדכון קטלוג מיידי ללא טעינת עמוד מחדש, טיפול נכון במספר סוגי מחירים, וערכי גבול מנתונים אמיתיים. צוות מפתחי 1C-Bitrix המוסמכים שלנו עם ניסיון של למעלה מ-5 שנים ו-50+ הטמעות פילטר מוצלחות מבטיח אינטגרציה חלקה. יישמנו עשרות פתרונות כאלה — מסליידר פשוט ועד סקאלה לוגריתמית לקטלוגים עם פיזור מחירים מ-$3–4.5k. פילטר מחירים מותאם זה עם סליידר AJAX וסקאלה לוגריתמית עולה על השדות הסטנדרטיים. רכיב הפילטר הסטנדרטי אינו מכסה תרחישים אלה ללא פיתוח מותאם אישית. צרו קשר לייעוץ על הפרויקט שלכם.
כיצד פועל פילטר המחירים ב-1C-Bitrix
הפילטר החכם יוצר פרמטר שאילתה כמו PRICE_1[MIN]=100&PRICE_1[MAX]=5000, כאשר 1 הוא מזהה סוג המחיר. CIBlockElement::GetList מקבל פרמטרים אלה דרך arFilter עם מפתחות >=PRICE ו-<=PRICE. כאשר קיימים מספר סוגי מחירים, הסינון מתבצע על המחיר הבסיסי או המחיר עבור קבוצת המשתמשים הנוכחית. התיעוד הרשמי של 1C-Bitrix ממליץ להשתמש במתודות של מודול הקטלוג המסחרי.
קבלת המחיר המינימלי והמקסימלי מהקטלוג לאתחול הסליידר:
// Получение граничных значений цены из раздела каталога
$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 דרך מאפייני // Получение граничных значений цены из раздела каталога $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']); } או JSON בתגית data-*.
כיצד ליישם סליידר עם טווח כפול
HTML טבעי אינו תומך בסליידר עם שני ידיות. היישום משתמש בשני <script> עם מיקום 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);
});
}בשרת, פרמטרי המחיר מתקבלים דרך input[type=range] ו-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); }); } } ומתווספים לפילטר // Инициализация 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); }); } . בעת שימוש ברכיב $_REQUEST['PRICE_1_MIN'], הנתונים מועברים אוטומטית אם הסליידר יוצר שדות טופס סטנדרטיים.
סינון לפי מספר סוגי מחירים
בקטלוגי 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>'; מקרה בוחן: קטלוג אלקטרוניקה עם טווח מחירים רחב
לקוח — חנות מקוונת למכשירי חשמל ביתיים — היה לו קטלוג עם מחירים שנעו בין $3–4.5k. שדות הקלט הסטנדרטיים "מ/עד" עבדו בצורה גרועה: משתמשים הזינו ערכים ידנית, ולעיתים קרובות עשו טעויות עם אפסים. הצענו סליידר עם סקאלה לוגריתמית — הטווח התחתון ($3–50) תופס את אותו שטח מסך כמו הטווח העליון ($1k–4.5k). כתוצאה מכך, הסליידר צמצם את זמן בחירת הטווח פי 2–4 בהשוואה לשדות קלט; חלקם של המשתמשים שהפעילו את פילטר המחירים עלה מ-12% ל-29%, וההמרה מהקטלוג המסונן הייתה גבוהה ב-18%. זה שיפור של פי 2.4, כלומר הסליידר טוב פי 2.4 משדות קלט. עלות הפרויקט הייתה $1,200 והניבה הכנסה חודשית נוספת של $3,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; // Округление до сотен
} למה לבחור בסליידר על פני שדות קלט?
השוו בין שתי הגישות:
| פרמטר | שדות מ/עד | סליידר עם טווח כפול |
|---|---|---|
| מהירות בחירת טווח | 8–12 שניות | 2–4 שניות |
| שגיאות קלט (אפסים נוספים) | ~15% מהסשנים | פחות מ-1% |
| תמיכה במובייל | בינונית (נדרשים מקלדות מספריות) | גבוהה (מחוות) |
| משוב חזותי | אין | כן (הדגשת טווח) |
הניסיון שלנו מראה שסליידר מיושם היטב מגדיל את ההמרה בקטגוריה ב-15–25%.
כיצד להאיץ את ביצועי הפילטר בקטלוגים גדולים?
עבור קטלוגים עם מאות אלפי פריטים, אנו משתמשים במטמון מתויג. בכל פעם שהקטלוג המסחרי משתנה (לדוגמה, עדכון מחיר מוצר), המטמון של הרכיבים המשויכים למוצר זה נמחק אוטומטית. בנוסף, אנו משתמשים בטעינה עצלה של מונה המוצרים ובשאילתות SQL מותאמות עם אינדקסים. אם פיזור המחירים עולה על שני סדרי גודל, אנו ממליצים על סקאלה לוגריתמית — היא הופכת את הסליידר לרגיש באופן אחיד על פני כל הטווח.
מה כלול בעבודה
לחצו להרחבה
| שלב | תיאור |
|---|---|
| ביקורת | ניתוח הקטלוג הנוכחי, סוגי מחירים, ביצועים |
| עיצוב | בחירת סקאלה (לינארית/לוגריתמית), אב טיפוס של סליידר |
| פיתוח | רכיב PHP, מחלקת JS, מטפל AJAX |
| אינטגרציה | הטמעה בתבנית, סנכרון עם הפילטר החכם |
| אופטימיזציה | מטמון מתויג, אינדקסים במסד נתונים |
| בדיקות | על כל קבוצות המשתמשים |
| תיעוד | הוראות תחזוקה |
כיצד ליישם (שלב אחר שלב)
- ביקורת: נתחו את הקטלוג וסוגי המחירים שלכם.
- עיצוב: בחרו סקאלה לינארית או לוגריתמית.
- פיתוח: צרו את רכיב ה-PHP ומחלקת ה-JS.
- אינטגרציה: הטמיעו בתבנית.
- אופטימיזציה: יישמו מטמון מתויג ואינדקסים במסד נתונים.
- בדיקות: ודאו על כל קבוצות המשתמשים.
- תיעוד: ספקו הוראות תחזוקה.
לוח זמנים ועלות
סליידר עם עדכוני AJAX ופרמטרים סטנדרטיים עבור סוג מחיר אחד אורך 2–3 ימי עבודה. הטמעה מלאה עם סקאלה לוגריתמית, מספר סוגי מחירים, מונה מוצרים וסנכרון עם הפילטר החכם אורכת 4–6 ימי עבודה. עלות הפרויקט הכוללת החל מ-$1,200, עם פוטנציאל להגדלת הכנסה חודשית העולה על $3,000. צרו קשר להערכה מדויקת.

