DoubleRange - Vanilla JavaScript Dual-Thumb Range Slider - DEMO page

https://github.com/ej-kon/DoubleRange

Example 1. Basic

			<div id="slider-1"></div>	
		
			const formatterFn = (v) => `$ ${v}`;
			const callbackFn = (from,to) => {console.log(`example1 from:${from} to:${to}`)};
			const slider = DoubleRange.create({
						selector: "#slider-1",
						min: 30,
						max: 500,
						from: 100,
						to: 400,
						step: 1,
						label: 'Example 1',
						formatter: formatterFn,
						delay: 300,
						callback: callbackFn
			});
		

Example 2. Basic CSS customization

			<div id="slider-2"></div>	
		
			const toEuro = (n) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ' €';
			const callbackFn = (from,to) => {console.log(`example2 from:${from} to:${to}`)};
			const slider = DoubleRange.create({
						selector: "#slider-2",
						min: 1015,
						max: 23050,
						from: 10000,
						to: 20000,
						step: 1,
						label: 'Example 2',
						formatter: toEuro,
						delay: 500,
						callback: callbackFn
			});
		
			#slider-2 .double-range { 
				--thumb-from-bg: #145056; 
				--thumb-to-bg: #95be08; 
				--range-bar-bg: linear-gradient(to right, #0c2e32, #145056);
				--thumb-size: 20px;
				/* custom padding , calc(var(--thumb-size) / 2) is the max for 100% width*/
				padding: 30px calc(var(--thumb-size) / 2);
				overflow: visible;
			}
			
			#slider-2 .double-range label
			{
				font-size: 13px;
				color:#222222;
				padding: 0px;
			}		
		

Example 3. CSS:Without start and end point JS:ensure that from and to have at least 100 distance

			<div id="slider-3"></div>	
		
			let slider = null;
			const beforeFromChangeFn = (from, to) => {
				if (to - from < 100) {
					slider.setFrom(to - 100);
					return false;
				}
				return true;
			};
			const beforeToChangeFn = (from, to) => {
				if (to - from < 100) {
					slider.setTo(from + 100);
					return false;
				}
				return true;
			};
			const formatterFn = (v) => `$ ${v}`;
			const callbackFn = (from, to) => { console.log(`example3 from:${from} to:${to}`) };
			slider = DoubleRange.create({
				selector: "#slider-3",
				min: 30,
				max: 500,
				from: 100,
				to: 400,
				step: 1,
				label: 'Example 3',
				formatter: formatterFn,
				beforeFromChange:beforeFromChangeFn,
				beforeToChange:beforeToChangeFn,
				delay: 250,
				callback: callbackFn
			});
		
			#slider-3 .double-range { 
				--thumb-from-bg: #003377; 
				--thumb-to-bg: #005599; 
				--range-bar-bg: linear-gradient(to right, #003377, #005599);
				padding: 40px calc(var(--thumb-size) / 2);
				--thumb-size: 30px;
				overflow: visible;
			}
			
			#slider-3 .double-range label
			{
				font-size: 13px;
				color:#222222;
				padding: 0px;
			}	
			
			#slider-3 .double-range .point {
			    display: none;
			}
			
			#slider-3 .double-range .track
			{
				background: none;
				box-shadow: inherit;
			}
			
			/* instead of the track line */
			#slider-3 .double-range::before {
			    position: absolute;
			    display: inline-block;
			    content: "";
			    left: 0px;
			    top: calc(50% - 5px);
			    width: 100%;
			    height: 10px;
			    background: linear-gradient(to right, #e1e3e7, #f1f3f7);
			    border-radius: 5px;
			    cursor: pointer;
			    box-shadow: 0px 0px 1px 1px rgba(100, 100, 100, 0.2);
			}
		

Example 4. Date range

			<div id="slider-4"></div>	
		
			const dateFormatter = (local) => {
				const intl = new Intl.DateTimeFormat(local);
				const parts = intl.formatToParts(new Date(2000, 0, 1))
					.filter(p => p.type !== 'literal');
				const order = parts.reduce((o, p, i) => (o[p.type] = i, o), {});
				const msPerDay = 864e5;
	
				return {
					fromInt: v => intl.format(new Date(v * msPerDay)),
					toInt: s => {
						const v = s.split(/\D+/);
						const y = +v[order.year];
						const m = +v[order.month] - 1;
						const d = +v[order.day];
						return Date.UTC(y, m, d) / msPerDay | 0;
					}
				};
			};
	
			const formatter = dateFormatter("en-US");
	
			const callbackFn = (from, to) => {
				console.log(
					`example:4 from:${formatter.fromInt(from)} to:${formatter.fromInt(to)}`);
			};
	
			const slider = DoubleRange.create({
				selector: "#slider-4",
				min: formatter.toInt("01/01/2026"),
				max: formatter.toInt("12/31/2026"),
				from: formatter.toInt("03/01/2026"),
				to: formatter.toInt("05/15/2026"),
				step: 1,
				label: 'Example 4',
				formatter: formatter.fromInt,
				delay: 500,
				callback: callbackFn
			});
		
			#slider-4 .double-range {
			    --thumb-from-bg: #005599;
			    --thumb-to-bg: #990055;
			    --range-bar-bg: linear-gradient(to right, #cee1f8, #f8cec1);
			}
			
			#slider-4 .double-range label
			{
				font-size: 13px;
				text-shadow:0px 0px 1px;
				letter-spacing:1px;
				padding: 0px;
			}
			
			#slider-4 .double-range label.from
			{
				color:#005599;
			}
			
			#slider-4 .double-range label.from::after
			{
				color:#000000;
			}
			
			#slider-4 .double-range label.to
			{
				color:#990022;
			}	
		

Example 5. setFrom() , setTo() , setMin() , setMax()

			<div class="fields">
				<div>
					<input type="text" id="from-5" placeholder="" size="6" maxlength="6">
					<button id="set-from-5">setFrom()</button>
				</div>
				<div>
					<input type="text" id="to-5" placeholder="" size="6" maxlength="6">
					<button id="set-to-5">setTo()</button>
				</div>		
				<div>
					<input type="text" id="min-5" placeholder="" size="6" maxlength="6">
					<button id="set-min-5">setMin()</button>
				</div>	
				<div>
					<input type="text" id="max-5" placeholder="" size="6" maxlength="6">
					<button id="set-max-5">setMax()</button>
				</div>		
				<div>
					<label for='callback-5'>result:</label>
					<input type="text" id="result-5" placeholder="" size="6" maxlength="6">
				</div>									
			</div>		
		
			const inp = {
				from: document.getElementById('from-5'),
				to: document.getElementById('to-5'),
				min: document.getElementById('min-5'),
				max: document.getElementById('max-5'),
				result: document.getElementById('result-5')
			}
			const btn = {
				from: document.getElementById('set-from-5'),
				to: document.getElementById('set-to-5'),
				min: document.getElementById('set-min-5'),
				max: document.getElementById('set-max-5'),
			};
	
			const toEuro = (n) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ' €';
			const callbackFn = (from, to) => inp.result.value = `from:${from} to:${to}`;
			const slider = DoubleRange.create({
				selector: "#slider-5",
				min: 5400,
				max: 19600,
				from: 8398,
				to: 15325,
				step: 1,
				label: 'Example 5',
				formatter: toEuro,
				delay: 150,
				callback: callbackFn
			});
	
			const pointError = (el) => {
				el.value = "";
				el.classList.add("error");
				setTimeout(()=>{el.classList.remove("error");},300);
			};
	
			btn.from.addEventListener('click', () => {
				if (!slider.setFrom(parseInt(inp.from.value))) {
					// setFrom and setTo return false if the value is invalid
					pointError(inp.from);
				}
			});
	
			btn.to.addEventListener('click', () => {
				if (!slider.setTo(parseInt(inp.to.value))) {
					pointError(inp.to);
				}
			});
	
			btn.min.addEventListener('click', () => {
				// setMin and setMax throw an error if the value is invalid
				try {
					slider.setMin(parseInt(inp.min.value));
				}
				catch (e) {
					console.error(e);
					pointError(inp.min);
				}
			});
	
			btn.max.addEventListener('click', () => {
				try {
					slider.setMax(parseInt(inp.max.value));
				}
				catch (e) {
					console.error(e);
					pointError(inp.max);
				}
			});	
		
			#slider-5 .double-range label
			{
				font-size: 13px;
				padding: 0px;
				text-shadow:0px 0px 1px;
				letter-spacing: 1px;
			}
			
			#slider-5 .double-range label.from>span
			{
				color:#06203b;
			}
			
			#slider-5 .double-range label.to>span
			{
				color:#194a7e;
			}