Search code examples
javascripthtmlcsshtml-input

How to create a scientific HTML slider with input type="range"


I need to create a scientific input slider. More precisely:

  • n ticks, example: no matter the range (0 - 200, 1 - 11, etc.), I want 11 ticks covering the whole range
  • the number value label below each tick
  • no "glue" attraction effect when the slider cursor is near a tick

Mockup:

enter image description here

Note:

The following code produces a HTML slider with ticks, and it works. However, it fails both the 2nd and 3rd criteria above.

input { width: 400px; }
<input type=range min=0 max=200 value=0 step=1 list=tickmarks>
<datalist id=tickmarks>
    <option>0</option>
    <option>20</option>
    <option>40</option>
    <option>60</option>
    <option>80</option>
    <option>100</option>
    <option>120</option>
    <option>140</option>
    <option>160</option>
    <option>180</option>
    <option>200</option>
 </datalist>

Is there an attribute for an HTML <input type="range"> to enable these "number-labelled" ticks?

Is an implementation that satisfies all three criteria possible?


Solution

  • As Lajos Arpad's answer indicates, it's best to avoid using datalist as your requirements go beyond what it's capable of. You'll need to manually create the ticks with number values.

    The following code uses a HTML layout similar to the original code in your question, with styling to suit. Note that the width is set on a parent element, and if the parent is too small then the tick numbers will wrap to a new line. To counter this you may need to decrease the tick number font size.

    To get the ticks lining up properly across all major browsers it is perhaps better to style the slider so it looks identical across browsers. Styles targeting Chrome- and Firefox-specific pseudo-elements for range sliders are included. The result should look the same across all Chromium and Mozilla based browsers.

    Note that a truly cross-browser method of filling in the range slider, as shown in the question, can only be achieved with JavaScript. The JavaScript here is basically copied from this answer

    const input = document.querySelector("#input input")
    input.addEventListener( 'input', function() {
        const value = (this.value-this.min)/(this.max-this.min)*100;
        this.style.background = 'linear-gradient(to right, #35b0f2 0%, #35b0f2 ' + value + '%, #ccc ' + value + '%, #ccc 100%)';
    } );
    #input {
        width: 400px;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
    }
    #input span {
        position: relative;
        margin: 15px -5px 0 -5px;
        flex-basis: 0;
        flex-grow: 1;
        text-align: center;
        font-size: 0.85em;
        user-select: none;
    }
    #input span::after {
        content: "";
        position: absolute;
        width: 1px;
        height: 8px;
        left: 0;
        right: 0;
        margin: auto;
        top: -12px;
        background-color: #ccc;
    }
    #input input {
        width: 100%;
        margin: 5px 10px;
        position: relative;
        background-color: #ccc;
        border-radius: 99px;
        z-index: 10;
        height: 7px;
        -webkit-appearance: none;
    }
    #input input::-moz-range-thumb {
        border: none;
        height: 16px;
        width: 16px;
        border-radius: 99px;
        background: #35b0f2;
        cursor: pointer;
    }
    #input input::-webkit-slider-thumb {
      box-shadow: none
      border: none;
      height: 16px;
      width: 16px;
      border-radius: 99px;
      background-color: #35b0f2;
      cursor: pointer;
      -webkit-appearance: none;
    }
    <div id="input">
        <input type=range min=0 max=200 value=0 step=1>
        <span>0</span>
        <span>20</span>
        <span>40</span>
        <span>60</span>
        <span>80</span>
        <span>100</span>
        <span>120</span>
        <span>140</span>
        <span>160</span>
        <span>180</span>
        <span>200</span>
    </div>