1

var slider = document.getElementById("myRange");
var output = document.getElementById("demo");
output.innerHTML = slider.value;

slider.oninput = function() {
  output.innerHTML = this.value;
}
.slidecontainer {
  width: 100%;
}

.slider {
  -webkit-appearance: none;
  width: 100%;
  height: 15px;
  border-radius: 5px;
  background: #d3d3d3;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
}

.slider:hover {
  opacity: 1;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #04AA6D;
  cursor: pointer;
}

.slider::-moz-range-thumb {
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #04AA6D;
  cursor: pointer;
}
<h1>Round Range Slider</h1>

<div class="slidecontainer">
  <input type="range" min="1" max="100" value="50" class="slider" id="myRange" style="width: 200px">
  <p>Value: <span id="demo"></span></p>
</div>

The code above is a range slider made in HTML.

enter image description here

This is example of how it could look.

2 Answers2

1

For the solution, first need to get the input range, then the width of step, because of the range will be greater than your maximum value, the bubble can't to place by center to the thumb. Finally multiply range and step to get the bubble position. The bubble better create with the svg create bubble. For styling the range input link

function App() {
  const [range, setRange] = React.useState(0);
  const [step, setStep] = React.useState(0);
  const ref = React.useRef(null);

  const getRange = (ev) => {
    setRange(ev.target.value);
  };

  React.useEffect(() => {
    const rangeLinePadding = 16;
    const calcStep =
      (ref.current.offsetWidth - rangeLinePadding) / ref.current.max;
    setStep(calcStep);
  }, []);

  return ( <div className="App">
      <h1> Range Slider </h1>
      <div className="slider">
        <input
          type="range"
          id="range"
          min="0"
          max="100"
          value={range}
          onChange={getRange}
          ref={ref}
        />
        <label
          htmlFor="range"
          style={{
            transform: `translateX(${range * step}px)`,
          }}>
          <span> {range} </span>
          <svg viewBox="0 0 15 18">
            <path d="M14,7a7,7,0,0,1-2,4.87L7.72,16.35a1,1,0,0,1-1.44,0L2,11.87A6.93,6.93,0,0,1,0,7,7,7,0,0,1,14,7Z" />
          </svg>
        </label>
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
.App {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 50px;
  font-family: sans-serif;
  text-align: center;
}

.slider {
  width: 200px;
  display: flex;
  flex-direction: column;
  position: relative;
  padding: 0 10px;
}

label[for='range'] {
  width: 35px;
  height: 40px;
  position: absolute;
  top: -45px;
  left: 0;
  color: hsl(0, 0%, 0%);
  pointer-events: none;
}

label[for='range'] span {
  position: absolute;
  top: 8px;
  left: 50%;
  text-align: center;
  font-size: 0.9em;
  font-weight: bold;
  transform: translateX(-50%);
  z-index: 5;
}

label[for='range'] svg {
  height: inherit;
  position: relative;
  left: 1px;
  fill: hsl(207, 90%, 77%);
  z-index: -1;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Anton
  • 8,058
  • 1
  • 9
  • 27
1

I know this is old, but I'm posting it here because I ran into the same problem. While Anton's answer is fine if your custom range component always has the minimum of 0, it breaks dow when that is not the case. So if you have a range with minimum 2 and maximum 10, the bubble will not be aligned properly.

To solve that, in the calcThumbStep we need to change the calculation to account for minimum

  React.useEffect(() => {
    const rangeLinePadding = 16;
    const calcStep =
      (ref.current.offsetWidth - rangeLinePadding) / (ref.current.max - ref.current.min);
    setStep(calcStep);
  }, []);

Later in the input element itself, in the translate calculation, we need to update it to account for when the element is at the left most position with a non 0 minimum.

    style={{
          transform: `translateX(${value * thumbStep - thumbStep * min}px)`,
        }}

For a full code reference check out Anton's answer and just swap in my changes. Hope this helps someone out there.