javascriptjqueryhtmlcss

Write value of range to thumb slider


I want to write the value of range input to the thumb. I looked to every single question about this I could find on Google and I didn't find a solution. I also want to when the value is 10001 I want it to show instead.

I have tried:

$('#km').on('input', function() {
  if ($(this).val() === "10001") {
    console.log("infinity");
    $(this).addClass("range-infinite")
  } else {
    $(this).removeClass("range-infinite");
  }
});
.range {
  -webkit-appearance: none;
  -moz-apperance: none;
  background-image: linear-gradient(to right, #1299E6, #A0D9F9);
  width: 90%;
  margin-left: 70px;
  height: 1px;
  margin-bottom: 40px;
  border-top: none;
}

.range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  -moz-apperance: none;
  height: 30px;
  width: 80px;
  background: #1299E6;
  border: 1px solid #A0D9F9;
  border-radius: 40px;
}

.range::-webkit-slider-thumb::before {
  content: attr(data-val)
}

.range-infinite::before {
  content: "∞";
}

.range input[type=range] {
  border: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="range-div">
  <input type="range" name="" id="" class="range">
</div>

but it doesn't seem to work.
Any help is appreciated.


Solution

  • :before and :after pseudo will not work on Form Action elements like input.
    Instead use a span for example.

    const els = (sel, par = document) => par.querySelectorAll(sel);
    
    els(".range").forEach((elRange) => {
      elRange.addEventListener("input", () => {
        let val = elRange.valueAsNumber;
        val = val >= parseInt(elRange.max, 10) ? "∞" : val;
        elRange.nextElementSibling.dataset.val = val;
      });
      elRange.dispatchEvent(new Event("input"));
    });
    .range ~ output::before {
      content: attr(data-val);
      margin-right: 0.2em;
    }
    <input type="range" name="" class="range" min=0 max=10001 step=1 value=0>
    <output>km</output>


    Create a custom range input

    If you want the <output> element to follow-up with your value you could:

    Position your output absolute in a relative parent.
    Calculate and transform your value into thumb position px or %.
    Here's an example that uses CSS variables:

    const els = (sel, par = document) => par.querySelectorAll(sel);
    const el = (sel, par = document) => par.querySelector(sel);
    
    els(".range").forEach((elRange) => {
      const elInput = el("input", elRange);
      const elOutput = el("output", elRange);
      const thumbWidth = 100; // Same as in CSS
    
      const max = parseInt(elInput.max, 10);
    
      const calc = () => {
        const val = elInput.valueAsNumber;
        const txt = val >= max ? "∞" : val;
        const xPX = val * (elInput.offsetWidth - thumbWidth) / max; // px
        const xPC = xPX * 100 / elInput.offsetWidth; // %
        elOutput.style.setProperty("--x", xPC);
        elOutput.dataset.val = txt;
      };
    
      elInput.addEventListener("input", calc);
      addEventListener("resize", calc);
      elInput.dispatchEvent(new Event("input"));
    });
    /* QuickReset */
    * {
      margin: 0;
      box-sizing: border-box;
    }
    
    html,
    body {
      min-height: 100%;
      font: 1rem/1.4 system-ui, sans-serif;
    }
    
    
    /* CUSTOM RANGE INPUT */
    
    .range {
      --thumbWidth: 100;
      --thumbHeight: 30;
      --sliderHeight: 10;
    
      position: relative;
      display: flex;
      height: calc(var(--thumbHeight) * 1px);
      touch-action: none;
      color: #fff;
      margin: 2em;
    
      input {
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        background-image: linear-gradient(to right, #1299E6, #A0D9F9);
        width: 100%;
        height: calc(var(--sliderHeight) * 1px);
        margin: auto 0;
        outline: none;
    
      }
    
      input:active::-webkit-slider-thumb {
        box-shadow: 0 2px 5px -2px rgba(0, 0, 0, 0.4);
      }
    
      input::-webkit-slider-thumb {
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        height: calc(var(--thumbHeight) * 1px);
        width: calc(var(--thumbWidth) * 1px);
        background: #1299E6;
        border-radius: 30px;
        cursor: grab;
        box-shadow: 0 0 0 0.2em #fff;
      }
    
      output {
        --x: 0;
        position: absolute;
        display: flex;
        align-items: center;
        justify-content: center;
        left: calc(var(--x) * 1%);
        width: calc(var(--thumbWidth) * 1px);
        height: 100%;
    
        font-size: 0.85em;
        pointer-events: none;
    
        &::before {
          content: attr(data-val);
          margin-right: 0.5em;
        }
      }
    }
    <div class="range">
      <input type="range" name="km" min=0 max=10001 step=1 value=0>
      <output>km</output>
    </div>