Lightweight JavaScript range slider with no dependencies
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive event system for responding to slider interactions and value changes with detailed callback information and namespacing support.
Binds an event listener to the slider.
/**
* Bind an event listener to the slider
* @param eventName - Event name, optionally with namespace (e.g., 'update.myNamespace')
* @param callback - Function to call when event fires
*/
slider.noUiSlider.on(eventName: string, callback: EventCallback): void;
type EventCallback = (
values: string[], // Formatted values for all handles
handle: number, // Index of the handle that triggered the event
unencoded: number[], // Raw numeric values for all handles
tap?: boolean, // True if triggered by tap/click
positions?: number[], // Handle positions as percentages (0-100)
slider?: SliderAPI // Reference to the slider API
) => void;Usage Examples:
// Basic event binding
slider.noUiSlider.on('update', function(values, handle) {
console.log('Handle', handle, 'updated to:', values[handle]);
});
// Access all callback parameters
slider.noUiSlider.on('slide', function(values, handle, unencoded, tap, positions, slider) {
console.log('Values:', values); // ["20.00", "80.00"] (formatted)
console.log('Handle:', handle); // 1 (active handle index)
console.log('Raw values:', unencoded); // [20, 80] (numeric)
console.log('Tap event:', tap); // true/false
console.log('Positions:', positions); // [20, 80] (percentages)
console.log('Slider API:', slider); // Reference to slider
});
// Event with namespace
slider.noUiSlider.on('update.validation', function(values) {
validateInput(values);
});Removes event listeners from the slider.
/**
* Remove event listeners from the slider
* @param eventName - Event name, namespace, or combination to remove
*/
slider.noUiSlider.off(eventName: string): void;Usage Examples:
// Remove all listeners for an event
slider.noUiSlider.off('update');
// Remove listeners for specific namespace
slider.noUiSlider.off('.validation');
// Remove specific event with namespace
slider.noUiSlider.off('update.validation');
// Remove all namespaced events
slider.noUiSlider.off('.myNamespace');Fired when user interaction begins (mousedown, touchstart).
slider.noUiSlider.on('start', callback);Usage:
slider.noUiSlider.on('start', function(values, handle, unencoded) {
console.log('Started dragging handle', handle);
document.body.style.cursor = 'grabbing';
});Fired continuously during user interaction (mousemove, touchmove).
slider.noUiSlider.on('slide', callback);Usage:
slider.noUiSlider.on('slide', function(values, handle, unencoded) {
// Real-time feedback during dragging
document.getElementById('current-value').textContent = values[handle];
});Fired when slider values change, from any source.
slider.noUiSlider.on('update', callback);Usage:
slider.noUiSlider.on('update', function(values, handle, unencoded) {
// Update form inputs, display values, etc.
document.getElementById('value-' + handle).value = unencoded[handle];
});Fired when user interaction ends and value has changed.
slider.noUiSlider.on('change', callback);Usage:
slider.noUiSlider.on('change', function(values, handle, unencoded) {
// Save to database, validate final value, etc.
console.log('Final value changed to:', values[handle]);
saveToDatabase(unencoded);
});Fired when values are set programmatically via set() or setHandle().
slider.noUiSlider.on('set', callback);Usage:
slider.noUiSlider.on('set', function(values, handle, unencoded) {
console.log('Value programmatically set to:', values[handle]);
});
// This will trigger the 'set' event
slider.noUiSlider.set([30, 70]);Fired when user interaction ends (mouseup, touchend).
slider.noUiSlider.on('end', callback);Usage:
slider.noUiSlider.on('end', function(values, handle, unencoded) {
console.log('Finished interacting with handle', handle);
document.body.style.cursor = '';
});Fired when hovering over the slider (requires 'hover' behaviour).
slider.noUiSlider.on('hover', callback);Usage:
// Enable hover behaviour
noUiSlider.create(element, {
start: 50,
range: { min: 0, max: 100 },
behaviour: 'hover'
});
// Listen for hover events
slider.noUiSlider.on('hover', function(value) {
// Note: hover callback receives single value, not array
console.log('Hovering over value:', value);
showTooltip(value);
});Use namespaces to organize and manage event listeners:
// Add namespaced listeners
slider.noUiSlider.on('update.ui', updateUI);
slider.noUiSlider.on('update.validation', validateValues);
slider.noUiSlider.on('change.persistence', saveToDatabase);
// Remove specific namespace
slider.noUiSlider.off('.ui'); // Removes only UI-related listeners
slider.noUiSlider.off('.validation'); // Removes only validation listeners
// Remove specific event with namespace
slider.noUiSlider.off('update.ui'); // Removes only UI update listener
// Multiple namespaces
slider.noUiSlider.on('update.ui.form', function(values) {
// Update form fields
});// Two-way binding with form inputs
const priceRange = document.getElementById('price-range');
const minInput = document.getElementById('min-price');
const maxInput = document.getElementById('max-price');
noUiSlider.create(priceRange, {
start: [100, 500],
connect: true,
range: { min: 0, max: 1000 }
});
// Update inputs when slider changes
priceRange.noUiSlider.on('update', function(values, handle) {
if (handle === 0) {
minInput.value = Math.round(values[0]);
} else {
maxInput.value = Math.round(values[1]);
}
});
// Update slider when inputs change
minInput.addEventListener('change', function() {
priceRange.noUiSlider.set([this.value, null]);
});
maxInput.addEventListener('change', function() {
priceRange.noUiSlider.set([null, this.value]);
});slider.noUiSlider.on('update.validation', function(values, handle, unencoded) {
const isValid = unencoded.every(value => value >= 10 && value <= 90);
if (!isValid) {
slider.target.classList.add('invalid');
showError('Values must be between 10 and 90');
} else {
slider.target.classList.remove('invalid');
hideError();
}
});let debounceTimer;
slider.noUiSlider.on('slide', function(values) {
// Clear previous timer
clearTimeout(debounceTimer);
// Set new timer for API call
debounceTimer = setTimeout(function() {
updateSearchResults(values);
}, 300);
});
// Immediate update on final change
slider.noUiSlider.on('change', function(values) {
clearTimeout(debounceTimer);
updateSearchResults(values);
});// Coordinate multiple related sliders
const slider1 = document.getElementById('slider1');
const slider2 = document.getElementById('slider2');
// When slider1 changes, update slider2's range
slider1.noUiSlider.on('update.coordination', function(values) {
const maxValue = parseFloat(values[0]);
slider2.noUiSlider.updateOptions({
range: {
min: 0,
max: maxValue
}
});
});// Dispatch custom DOM events
slider.noUiSlider.on('change', function(values, handle, unencoded) {
const event = new CustomEvent('slider-changed', {
detail: {
values: values,
unencoded: unencoded,
handle: handle,
slider: this
}
});
document.dispatchEvent(event);
});
// Listen for custom events elsewhere
document.addEventListener('slider-changed', function(event) {
console.log('Slider changed:', event.detail);
});Formatted values for all handles using the format.to() function.
// With custom formatter
format: {
to: value => '$' + Math.round(value)
}
// values array contains: ["$25", "$75"]Zero-based index of the handle that triggered the event.
slider.noUiSlider.on('update', function(values, handle) {
if (handle === 0) {
console.log('First handle changed');
} else if (handle === 1) {
console.log('Second handle changed');
}
});Raw numeric values for all handles, not processed by formatters.
// Always contains actual numeric values
// unencoded: [25, 75] (regardless of formatting)True if the event was triggered by a tap/click action.
slider.noUiSlider.on('change', function(values, handle, unencoded, tap) {
if (tap) {
console.log('Value changed by clicking');
} else {
console.log('Value changed by dragging');
}
});Handle positions as percentages (0-100).
slider.noUiSlider.on('update', function(values, handle, unencoded, tap, positions) {
console.log('Handle positions:', positions); // [25, 75] (percentages)
});Reference to the slider API object.
slider.noUiSlider.on('update', function(values, handle, unencoded, tap, positions, sliderAPI) {
// Access other slider methods
const allValues = sliderAPI.get();
const options = sliderAPI.options;
});Event handling is generally robust, but be aware of:
// Events fire even for invalid operations
slider.noUiSlider.on('update', function(values) {
try {
updateUI(values);
} catch (error) {
console.error('Error updating UI:', error);
}
});
// Prevent infinite loops in cross-updates
let updating = false;
slider.noUiSlider.on('update', function(values) {
if (updating) return;
updating = true;
// Update related elements
updateRelatedSlider(values);
updating = false;
});Install with Tessl CLI
npx tessl i tessl/npm-nouislider