<div id="root"></div>
$thumb-size: 16px;
@mixin track-styles {
appearance: none;
background: transparent;
border: transparent;
}
@mixin thumb-styles {
appearance: none;
pointer-events: all;
width: $thumb-size;
height: $thumb-size;
border-radius: 0px;
border: 0 none;
cursor: grab;
background-color: red;
&:active {
cursor: grabbing;
}
}
#root {
max-width: 500px;
padding: 12px;
margin: auto;
}
.wrapper {
position: relative;
display: flex;
align-items: center;
margin: 40px calc(#{$thumb-size} / 2);
padding-top: 1.6rem;
height: calc(#{$thumb-size} + 1.6rem);
}
.input-wrapper {
width: calc(100% + #{$thumb-size});
margin: 0 calc(#{$thumb-size} / -2);
position: absolute;
height: $thumb-size;
}
.control-wrapper {
width: 100%;
position: absolute;
height: $thumb-size;
}
.input {
position: absolute;
width: 100%;
pointer-events: none;
appearance: none;
height: 100%;
opacity: 0;
z-index: 3;
padding: 0;
&::-ms-track {
@include track-styles;
}
&::-moz-range-track {
@include track-styles;
}
&:focus::-webkit-slider-runnable-track {
@include track-styles;
}
&::-ms-thumb {
@include thumb-styles;
}
&::-moz-range-thumb {
@include thumb-styles;
}
&::-webkit-slider-thumb {
@include thumb-styles;
}
};
.rail {
position: absolute;
width: 100%;
top: 50%;
transform: translateY(-50%);
height: 6px;
border-radius: 3px;
background: lightgrey;
}
.inner-rail {
position: absolute;
height: 100%;
background: hotpink;
opacity: 0.5;
}
.control {
width: $thumb-size;
height: $thumb-size;
border-radius: 50%;
position: absolute;
background: hotpink;
top: 50%;
margin-left: calc(#{$thumb-size} / -2);
transform: translate3d(0, -50%, 0);
z-index: 2;
}
View Compiled
import React from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";
const RangeSlider = ({ min, max, value, step, onChange }) => {
const [minValue, setMinValue] = React.useState(value ? value.min : min);
const [maxValue, setMaxValue] = React.useState(value ? value.max : max);
React.useEffect(() => {
if (value) {
setMinValue(value.min);
setMaxValue(value.max);
}
}, [value]);
const handleMinChange = e => {
e.preventDefault();
const newMinVal = Math.min(+e.target.value, maxValue - step);
if (!value) setMinValue(newMinVal);
onChange({ min: newMinVal, max: maxValue });
};
const handleMaxChange = e => {
e.preventDefault();
const newMaxVal = Math.max(+e.target.value, minValue + step);
if (!value) setMaxValue(newMaxVal);
onChange({ min: minValue, max: newMaxVal });
};
const minPos = ((minValue - min) / (max - min)) * 100;
const maxPos = ((maxValue - min) / (max - min)) * 100;
return (
<div class="wrapper">
<div class="input-wrapper">
<input
class="input"
type="range"
value={minValue}
min={min}
max={max}
step={step}
onChange={handleMinChange}
/>
<input
class="input"
type="range"
value={maxValue}
min={min}
max={max}
step={step}
onChange={handleMaxChange}
/>
</div>
<div class="control-wrapper">
<div class="control" style={{ left: `${minPos}%` }} />
<div class="rail">
<div
class="inner-rail"
style={{ left: `${minPos}%`, right: `${100 - maxPos}%` }}
/>
</div>
<div class="control" style={{ left: `${maxPos}%` }} />
</div>
</div>
);
};
const App = () => {
const [value, setValue] = React.useState({ min: 0, max: 100 });
return (
<div>
<RangeSlider min={0} max={100} step={5} value={value} onChange={setValue} />
<p>The min value is: <span>{value.min}</span></p>
<p>The max value is: <span>{value.max}</span></p>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
)
View Compiled
This Pen doesn't use any external JavaScript resources.