Современная лёгкая (~7 КБ) JS-библиотека без зависимостей для создания слайдеров диапазона с одной или двумя ручками перетаскивания.
- Глубокая настройка через CSS
- Плоский скин Coral по умолчанию, готовый Classic-скин и собственные скины через CSS-переменные, с динамическим переключением
- Поддержка сенсорного управления (touch)
- Поддержка отрицательных и дробных значений
- Отсутствие зависимостей
- Поддержка всеми основными браузерами
Добавьте <input type="range"> — класс на нём не нужен, вся базовая стилизация (включая ту, что
предотвращает «прыжок» разметки при инициализации, см. ниже) завязана на сам селектор input[type="range"],
а не на класс. Чтобы получить слайдер диапазона (две ручки), добавьте атрибут data-points
с начальным значением верхней ручки — иначе будет одна ручка. Значение — по сути список через
запятую, задел на будущее для нескольких дополнительных ручек, но сегодня используется только
первое число (максимум две ручки на слайдер).
<!-- одна ручка -->
<input type="range" min="0" max="100" step="1" value="40" />
<!-- две ручки -->
<input type="range" min="0" max="100" step="1" value="20" data-points="80" />Инициализируйте библиотеку на каждом слайдере (target может быть CSS-селектором или самим элементом):
document.querySelectorAll('input[type="range"]').forEach((slider) => {
new Ranger(slider, {
scaleTicksCount: 10,
scaleTickPrefix: '',
scaleTickSuffix: '',
labelIsVisible: true,
labelPrefix: '',
labelSuffix: '',
disabled: false,
});
});Ranger всё равно добавляет класс ranger-input на сам <input> при инициализации (настраивается через
classes.input) — как стабильный хук для собственных CSS-селекторов или querySelectorAll, но для
самой стилизации он не требуется.
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
classes |
object | { container: 'ranger', input: 'ranger-input', fill: 'ranger-fill', inputTo: 'ranger-input--to', scale: 'ranger-scale', scaleTick: 'ranger-scale-tick', scaleMinorTick: 'ranger-scale-tick ranger-scale-tick--minor', scaleTickLimit: 'ranger-scale-tick--limit', label: 'ranger-label', labelItem: 'ranger-label-item' } |
Имена классов для каждого элемента, создаваемого Ranger. input добавляется на сам <input> при инициализации как удобный хук — сама стилизация от него не зависит (см. «Использование» выше), в CSS <input> выбирается селектором input[type="range"]. scaleTickLimit добавляется поверх обычного класса деления (не заменяет его) — см. minValue/maxValue. |
scaleTicksCount |
number | 10 |
Количество подписанных (крупных) делений шкалы. 0 — полностью скрыть шкалу. |
scaleMinorTicksCount |
number | 0 |
Непомеченные мелкие деления между каждой парой крупных, равномерно делящие промежуток. |
scaleAnimatedTicksCount |
number | 1 |
Радиус (в делениях) с каждой стороны от ручки, на котором живёт дуга --ranger-scale (см. ниже): 1 (максимум) прямо у ручки, плавный (не линейный) спад к 0 на дальнем краю этого радиуса. Один общий радиус, применяется симметрично. |
scaleTickPrefix |
string | '' |
Текст перед значением каждого деления. |
scaleTickSuffix |
string | '' |
Текст после значения каждого деления. |
labelIsVisible |
boolean | true |
Показать/скрыть плавающую подпись(и) значения над ручкой(ами). |
labelPrefix |
string | '' |
Текст перед значением подписи. |
labelSuffix |
string | '' |
Текст после значения подписи. |
labelOnDragOnly |
boolean | false |
Показывать подпись, только пока ручка активно перетаскивается или перемещается клавиатурой. |
disabled |
boolean | false |
Отключить перетаскивание слайдера. |
fromInput |
HTMLInputElement | null |
Внешний <input>, синхронизируемый с нижней/единственной ручкой. |
toInput |
HTMLInputElement | null |
Внешний <input>, синхронизируемый с верхней ручкой (только режим диапазона). |
values |
array | null |
Отображаемые значения (подписи, даты, ценовые уровни...) для выбора по индексу — min/max/step вычисляются автоматически, а scaleTicksCount по умолчанию равен одному делению на значение (оба — если явно не переопределены). Задаёт format по умолчанию, если он не указан. |
format |
(value) => string |
null |
Переопределяет labelPrefix/labelSuffix и scaleTickPrefix/scaleTickSuffix везде, где отображается значение. Возвращённая строка (как и labelPrefix/labelSuffix) рендерится во всплывающей подписи как HTML, а не как экранированный текст — можно вернуть разметку (иконку, <strong> и т.п.). |
logScale |
boolean | false |
Отображает/форматирует значение экспоненциально между min и max (должно быть > 0); перетаскивание остаётся линейным. Задаёт format по умолчанию, если он не указан. Начальные value/data-points в разметке задаются в реальных единицах (например, цена $10) — Ranger сам переводит их в линейную позицию при инициализации, вычислять её вручную не нужно. |
minValue |
number | null |
Ограничивает результат каждого перетаскивания/навигации снизу, не трогая нативные min/max — шкала, деления и подпись продолжают охватывать весь диапазон (например, целый месяц values), а выбираемым остаётся только более узкое окно внутри него. Деление шкалы, совпавшее с этим значением, получает класс scaleTickLimit и остаётся видимым целиком, даже если адаптивное прореживание скрыло бы его. |
maxValue |
number | null |
То же самое сверху — вместе с minValue ограничивает выбор диапазоном [minValue, maxValue] внутри полной шкалы, с тем же выделением деления. |
snapPoints |
number[] | [] |
Значения, магнитно притягивающие ручку при приближении в пределах snapThreshold. |
snapThreshold |
number | 0.02 |
Радиус притяжения как доля диапазона min–max. |
fineStep |
number | null |
Шаг для точной навигации Shift+стрелка; по умолчанию step / 10. |
minGap |
number | 0 |
Минимальное расстояние между двумя ручками (только режим диапазона). Игнорируется, если задан fixedRange. |
fixedRange |
boolean | number | false |
Только режим диапазона. Фиксирует расстояние между ручками: за какую ни потяни, обе двигаются вместе, сохраняя ширину диапазона — например, часовой слот записи на приём, который можно перенести на другое время, но нельзя растянуть или сжать. true фиксирует то расстояние, с которым слайдер смонтирован (data-points минус value); число задаёт конкретную ширину явно. |
fillGradient |
string | null |
CSS background (обычно linear-gradient), закрашивающий заливку вместо обычного accent-color — например, зелёный→жёлтый→красный для индикатора риска/температуры. Растягивается по собственному прямоугольнику заливки, поэтому в режиме диапазона охватывает выбранный поддиапазон, а не фиксированную позицию на всей дорожке. |
marks |
array | [] |
Фиксированные точки/зоны на дорожке, независимые от шкалы делений: числа или { value, label?, className? } для точки, { from, to, label?, className? } — для зоны (например, «комфортный диапазон» рядом с отметкой рекомендованного значения). |
| Callback | Когда вызывается |
|---|---|
onStart |
начало drag |
onChange |
изменение значения |
onEnd |
окончание drag |
onFocus |
получение фокуса |
onBlur |
потеря фокуса |
Каждый колбэк получает (value, slider): value — это текущее значение (или [from, to] в режиме
диапазона), а slider — конкретная ручка (<input>), вызвавшая событие. При перетаскивании самой
заливки (см. ниже) slider — это элемент заливки, а value — пара [from, to].
new Ranger('#price', {
onStart: (value, slider) => console.log('начало:', value),
onChange: (value, slider) => console.log('изменение:', value),
onEnd: (value, slider) => console.log('окончание:', value),
onFocus: (value, slider) => console.log('фокус:', value),
onBlur: (value, slider) => console.log('потеря фокуса:', value),
});Некоторые взаимодействия работают автоматически, без отдельной опции для включения:
- Двойной клик/тап по ручке возвращает её к значению, с которого она начиналась.
- Перетаскивание самой заливки (только режим диапазона) сдвигает обе ручки вместе, сохраняя расстояние между ними — как перемещение целого окна диапазона дат вместо изменения его размера.
- Стрелки на сфокусированной ручке сдвигают её на
step, а с зажатым Shift — наfineStep. Home/End/PageUp/PageDown по-прежнему обрабатываются самим браузером.
Каждое деление — крупное или мелкое — также несёт живое CSS-свойство --ranger-scale (0–1) —
дугу с пиком 1 прямо у ближайшей ручки, плавно (не линейно) спадающую к 0 на дальнем краю
радиуса scaleAnimatedTicksCount, — чтобы обычный CSS мог его анимировать.
.ranger-scale-tick--animated поставляется с библиотекой как готовый пример этого — добавьте его через
classes.scaleTick/classes.scaleMinorTick, чтобы деление увеличивалось в высоту и проявлялось по
непрозрачности при прохождении ручки мимо:
new Ranger(slider, {
classes: {
scaleTick: 'ranger-scale-tick ranger-scale-tick--animated',
scaleMinorTick: 'ranger-scale-tick ranger-scale-tick--minor ranger-scale-tick--animated',
},
});Либо напишите собственную реакцию на --ranger-scale:
.ranger-scale-tick::before {
height: calc(4px + var(--ranger-scale, 0) * 12px);
transition: height 0.15s ease-out;
}Крупные деления — сама чёрточка и подпись вместе — также автоматически прореживаются — при каждом
изменении отображаемой ширины слайдера (отслеживается через ResizeObserver, а не только при
загрузке страницы) измеряется самая широкая подпись, и скрывается каждое N-е деление целиком, ровно
настолько, чтобы ни одна подпись не накладывалась на другую.
| Метод | Описание |
|---|---|
setValue(value) |
Программно перемещает нижнюю/единственную ручку — через тот же конвейер, что и обычное взаимодействие (step/snapPoints/minValue/maxValue, заливка, подпись, шкала, onChange, внешние поля). |
setRange(from, to) |
То же самое для обеих ручек сразу (только режим диапазона; иначе выбрасывает исключение). Оба значения применяются до того, как сработает конвейер, поэтому minGap/fixedRange видят настоящую целевую пару, а не устаревшую. |
update(options) |
Применяет часть опций после монтирования — min/max/step/disabled/snapPoints/minValue/maxValue/minGap/fineStep/fixedRange/format/fillGradient/labelIsVisible/marks и другие. Шкала и метки перестраиваются, если изменилось что-то, из чего строится их DOM (min/max/values/scaleTicksCount/scaleMinorTicksCount/format/marks); подпись создаётся или удаляется при переключении labelIsVisible. |
const ranger = new Ranger('#price', { min: 0, max: 1000 });
ranger.setValue(250);
// ranger.setRange(100, 400); — только для диапазонного слайдера
ranger.update({ max: 5000, disabled: false });marks добавляет фиксированные точки на дорожку независимо от шкалы делений scaleTicksCount —
удобно для отметки рекомендованного значения, прошлого среднего или любой другой опорной точки,
которая не должна зависеть от количества и шага обычных делений:
new Ranger('#price', {
marks: [
500,
{ value: 750, label: 'Рекомендовано', className: 'ranger-mark--highlight' },
{ from: 200, to: 400, label: 'Комфортная зона' },
],
});Точечная метка (value) — элемент .ranger-mark (класс настраивается через classes.markItem).
Метка-зона (from/to) — тот же элемент, но с классом classes.markRange (по умолчанию ещё и с
.ranger-mark--range) и шириной, растянутой на весь диапазон from–to, а не точкой. Обе используют
--ranger-mark-color как цвет по умолчанию и необязательную подпись label внутри <ins>.
Метки позиционируются в процентах — так же, как заливка: дорожка каждого встроенного скина растянута
на всю ширину без отступа под ручку (см. отрицательный отступ ::-webkit-slider-runnable-track/
::-moz-range-track в разделе «Скины» ниже), поэтому дополнительная поправка на ширину ручки метке
не нужна.
Все размеры и цвета слайдера — высота дорожки, скругление углов, цвет заливки/акцента, размеры и форма ручки, деления шкалы — заданы CSS-переменными, а не жёстко закодированными значениями, поэтому внешний вид можно полностью переопределить, вплоть до другого силуэта, а не только палитры:
| Переменная | По умолчанию (скин Coral) | Назначение |
|---|---|---|
--ranger-height |
6px |
Высота дорожки. |
--ranger-radius |
0 |
Скругление углов дорожки и заливки. |
--ranger-gap-top |
2.5rem |
Отступ над дорожкой, зарезервированный под всплывающую подпись. |
--ranger-gap-bottom |
4rem |
Отступ под дорожкой, зарезервированный под шкалу делений. |
--ranger-bg |
#e2e2e2 |
Фон дорожки. |
--ranger-accent |
#ff6f59 |
Цвет заливки, accent-color, обводка фокуса, подсветка анимированных делений. |
--ranger-thumb-width |
1.25rem |
Ширина ручки. |
--ranger-thumb-height |
2rem |
Высота ручки. |
--ranger-thumb-radius |
0 |
Скругление углов ручки. |
--ranger-thumb-bg |
transparent |
Фон ручки. |
--ranger-thumb-hover-bg |
transparent |
Фон ручки при наведении. |
--ranger-thumb-border |
transparent |
Рамка ручки. |
--ranger-thumb-icon |
initial (нет) |
Иконка на ручке. В скине Coral сама полоска-ручка рисуется через mask на псевдоэлементе, а не через эту переменную — она используется по назначению в скине Classic (см. ниже). |
--ranger-thumb-shadow |
none |
box-shadow ручки. |
--ranger-tick-color |
#bdbdbd |
Цвет линии деления. |
--ranger-tick-label-color |
#888 |
Цвет подписи деления. |
--ranger-mark-color |
#f59e0b |
Цвет метки по умолчанию (marks), если у неё нет своего className. |
Скин Coral — плоский серо-коралловый стиль в духе скинов Ion.RangeSlider: прямые углы вместо скруглённых и тонкая полоска-ручка вместо квадратной с иконкой — применяется по умолчанию, без каких-либо атрибутов.
Готовый скин Classic — прежний внешний вид: скруглённая дорожка и квадратная ручка с иконкой-
грипсами. Включается атрибутом data-skin="classic" — на самом слайдере или на любом его родителе
(например, на <html>, чтобы разом переодеть все слайдеры на странице):
<div data-skin="classic">
<input type="range" min="0" max="100" step="1" value="30" />
</div>Поскольку это чистые CSS-переменные, уже смонтированный слайдер переодевается сразу, как только
атрибут меняется — без повторной инициализации. Исключение — сама заливка: её цвет читается через
getComputedStyle(...).accentColor один раз на каждое изменение значения (см. fillSlider в
index.js), а не отслеживается непрерывно. Чтобы обновить уже отрисованные слайдеры мгновенно (не
дожидаясь следующего перетаскивания), достаточно вызвать fillSlider() у каждого экземпляра —
все смонтированные инстансы доступны через Ranger.instances:
document.getElementById('skin-switcher').addEventListener('change', (event) => {
document.documentElement.dataset.skin = event.target.value; // '', 'classic', ...
Ranger.instances.forEach((instance) => instance.fillSlider());
});Все встроенные скины разделяют один и тот же приём: ::-webkit-slider-runnable-track/
::-moz-range-track получают отрицательный margin-inline, равный половине --ranger-thumb-width,
что отменяет стандартное смещение центра ручки от края дорожки — благодаря этому ручка при любом
скине доходит до самого края дорожки, а marks и заливка совпадают с ней по проценту без какой-либо
ручной настройки.
Свой скин описывается точно так же — своим набором переопределений под собственным атрибутом или классом.
Каждая ручка — это обычный нативный <input type="range">, поэтому она изначально фокусируется по
Tab, читается скринридерами как ползунок (role="slider") и управляется стрелками/Home/End/PageUp/
PageDown без какого-либо кода со стороны Ranger. Библиотека дополняет это следующим:
aria-valuetext— обновляется на каждое изменение значения и озвучивает то же самое, что видно на экране (сопоставленный элементvalues, значениеlogScale,format,labelPrefix/labelSuffix), а не «сырое» число или индекс, которое иначе прочитал бы скринридер.aria-hidden="true"— на заливке, шкале делений и всплывающей подписи: это чисто визуальные дубликаты значения, уже доступного через саму ручку, и без этого атрибута скринридер озвучивал бы каждое значение по два-три раза.- Видимый фокус —
:focus-visibleрисует чёткую обводку вокруг ручки для клавиатурной навигации, так как кастомные::-webkit-slider-thumb/::-moz-range-thumbне всегда получают стандартную обводку фокуса браузера.
Направление наследуется через CSS от dir="rtl" (на самом слайдере или любом родителе) — отдельная
опция не нужна. Как только направление меняется, Ranger автоматически отражает:
- заливку и деления шкалы (через логические CSS-свойства
inset-inline-*, а неleft/right); - позицию всплывающей подписи;
- клик по дорожке (
positionToValue) и перетаскивание всего диапазона за заливку — оба учитывают направление, чтобы «вправо» и «влево» соответствовали ожидаемому визуальному смыслу.
<div dir="rtl">
<input type="range" min="0" max="100" step="1" value="30" />
</div>npm install
npm start # dev-сервер с демо на src/view/index.html
npm run build # production-сборка в dist/