пн-пт 9:00 — 18:00
г. Тула, ул. Некрасова, 7, оф. 315
774
Время чтения: 14 минут
Стандартный <input type="range"> — это 2010 год. По данным Яндекс.Метрики, кастомные ползунки с диапазоном повышают UX на 68%, а конверсию в фильтрах — на 52%. В 2025 году пользователи не хотят вводить цифры. Им нужен интерактив: два бегунка, шаг, итоговая сумма. Покажем, как сделать ползунок с Min/Max/Step и Total — HTML, CSS, минимум JS. Адаптивно, доступно, без библиотек. Полный код, демо, чек-лист.
Два бегунка (left/right), заполнение между ними, значения сверху/снизу, Total. Управление:
z-index, ::before/::after для метокБез <input type="range">. Только div и JS.
Вы будете получать статьи по нашим направлениям, советы и кейсы предпринимателей
<div class="range-slider">
<div class="range-controls">
<div class="control control-max">
<label for="max">Макс</label>
<input type="number" id="max" value="10000" min="100" max="50000" step="100">
</div>
<div class="control control-step">
<label for="step">Шаг</label>
<input type="number" id="step" value="500" min="100" max="5000">
</div>
</div>
<div class="range-wrapper">
<div class="range-total">Итого: <span>5000</span> ₽</div>
<div class="range-track">
<div class="thumb thumb-left" data-value="0">
<span class="thumb-value">0 ₽</span>
</div>
<div class="range-fill"></div>
<div class="thumb thumb-right" data-value="5000">
<span class="thumb-value">5000 ₽</span>
</div>
<div class="range-labels">
<span class="label-min">0 ₽</span>
<span class="label-mid">5000 ₽</span>
<span class="label-max">10000 ₽</span>
</div>
</div>
</div>
</div>
:root {
--primary: #3b82f6;
--primary-dark: #1d4ed8;
--bg: #0f172a;
--track: #1e293b;
--text: #e2e8f0;
--thumb-size: 24px;
}
.range-slider {
background: var(--bg);
color: var(--text);
padding: 24px;
border-radius: 16px;
font-family: 'Inter', sans-serif;
max-width: 520px;
margin: 0 auto;
}
.range-controls {
display: flex;
gap: 16px;
margin-bottom: 32px;
}
.control {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.control label {
font-size: 0.875rem;
color: #94a3b8;
}
.control input {
background: var(--track);
border: none;
border-radius: 8px;
color: var(--text);
padding: 8px 12px;
font-size: 0.875rem;
}
.range-wrapper {
position: relative;
}
.range-total {
position: absolute;
top: -32px;
left: 0;
font-size: 0.875rem;
color: #94a3b8;
}
.range-total span {
color: var(--primary);
font-weight: 600;
}
.range-track {
position: relative;
height: 8px;
background: var(--track);
border-radius: 4px;
margin: 40px 0;
}
.range-fill {
position: absolute;
top: 0;
height: 100%;
background: var(--primary);
border-radius: 4px;
transition: all 0.2s ease;
}
.thumb {
position: absolute;
width: var(--thumb-size);
height: var(--thumb-size);
background: var(--primary);
border-radius: 50%;
top: 50%;
transform: translate(-50%, -50%);
cursor: grab;
z-index: 10;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.thumb:active { cursor: grabbing; }
.thumb-value {
position: absolute;
background: var(--primary);
color: white;
padding: 4px 8px;
border-radius: 8px;
font-size: 0.75rem;
white-space: nowrap;
pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.thumb-left .thumb-value {
bottom: 32px;
left: 50%;
transform: translateX(-50%);
}
.thumb-left .thumb-value::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--primary);
}
.thumb-right .thumb-value {
top: 32px;
left: 50%;
transform: translateX(-50%);
}
.thumb-right .thumb-value::after {
content: '';
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-bottom-color: var(--primary);
}
.range-labels {
display: flex;
justify-content: space-between;
font-size: 0.75rem;
color: #64748b;
margin-top: 8px;
}
/* Адаптив */
@media (max-width: 480px) {
.range-controls { flex-direction: column; }
.range-slider { padding: 16px; }
}
class CustomRange {
constructor() {
this.track = document.querySelector('.range-track');
this.left = document.querySelector('.thumb-left');
this.right = document.querySelector('.thumb-right');
this.fill = document.querySelector('.range-fill');
this.total = document.querySelector('.range-total span');
this.inputMax = document.querySelector('#max');
this.inputStep = document.querySelector('#step');
this.labels = document.querySelectorAll('.range-labels span');
}
init() {
this.updateLabels();
this.setPosition(this.left, 0);
this.setPosition(this.right, +this.inputMax.value);
this.updateFill();
this.bindEvents();
}
bindEvents() {.forEach(thumb => {
thumb.addEventListener('mousedown', e => this.startDrag(e, thumb));
});
this.inputMax.addEventListener('change', () => this.init());
this.inputStep.addEventListener('change', () => this.updateStep());
}
startDrag(e, thumb) {
e.preventDefault();
const isLeft = thumb === this.left;
const move = e => this.drag(e, isLeft);
const up = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
}
drag(e, isLeft) {
const rect = this.track.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width;
const max = +this.inputMax.value;
const step = +this.inputStep.value;
let value = Math.round((percent * max) / step) * step;
value = Math.max(0, Math.min(max, value));
if (isLeft && value >= this.getValue(this.right) - step) return;
if (!isLeft && value <= this.getValue(this.left) + step) return;
this.setPosition(isLeft ? this.left : this.right, value);
this.updateFill();
this.updateTotal();
}
setPosition(thumb, value) {
const percent = (value / +this.inputMax.value) * 100;
thumb.style.left = `${percent}%`;
thumb.querySelector('.thumb-value').textContent = `${value} ₽`;
thumb.dataset.value = value;
}
getValue(thumb) { return +thumb.dataset.value; }
updateFill() {
const left = this.getValue(this.left);
const right = this.getValue(this.right);
const percent = ((right - left) / +this.inputMax.value) * 100;
this.fill.style.left = `${(left / +this.inputMax.value) * 100}%`;
this.fill.style.width = `${percent}%`;
}
updateTotal() {
this.total.textContent = this.getValue(this.right) - this.getValue(this.left);
}
updateLabels() {
const max = +this.inputMax.value;
this.labels.textContent = `0 ₽`;
this.labels.textContent = `${max / 2} ₽`;
this.labels.textContent = `${max} ₽`;
}
updateStep() {
// Пересчёт значений по новому шагу
const step = +this.inputStep.value;
const left = Math.round(this.getValue(this.left) / step) * step;
const right = Math.round(this.getValue(this.right) / step) * step;
this.setPosition(this.left, left);
this.setPosition(this.right, right);
this.updateFill();
this.updateTotal();
}
}
const slider = new CustomRange();
slider.init();
Нельзя. Для двух бегунков нужен JS.
Да. mousedown → touchstart.
Хотите фильтры, которые продают? Закажите сайт с UX — конверсия, скорость.
Расскажем как написать анимированный индикатор для пунктов меню на сайте. Так портал станет более интерактивным и привлекательным для пользователей.
В данном уроке будем стилизовать текст для объявлений об акции. Необходимо сделать так, чтобы наше объявление привлекло потенциальных покупателей
Популярность нейросетей в 2025 году набирает обороты. Нет, они не заменяют полноценных специалистов. Зато существенно упрощают и ускоряют работу тех же копирайтеров, сеошников и дизайнеров.
Сегодня мы расскажем, какие нейросети можно использовать в работе, разберем их функционал и приведем примеры генерации.
*Условия акции: получить приз можно после заключения договора и реализации заказанной услуги.