A range input for scientific applications

Here's a range input. Notice how it jumps immediately to the position you click.

<input type="range" 
    step="1"
    min="0"
    max="100"
    value="25" />
                

What if you want to fine-tune the value by incrementing and decrementing it by a predictable step? You can do this fine-tuning by focusing the input and using the arrow keys to adjust the value up and down.

You can make the click event behave in the same way by filtering the input's oninput event, like this:

<input type="range" 
    step="1"
    min="0"
    max="100"
    value="25"
    onmousedown="onMouseDown()"
    onmouseup="onMouseUp()"
    onmousemove="onMouseMove()"
    oninput="onInput(this)" />
var savedValue = 25;
var isMouseDown = false;
var isDragging = false;

function onMouseDown() {
    isMouseDown = true;
}
function onMouseUp() {
    isMouseDown = false;
    isDragging = false;
}
function onMouseMove() {
    if (isMouseDown) {
        isDragging = true;
    }
}

function onInput(input) {
    var step = new Number(input.step);
    var newVal = new Number(input.value);
    var oldVal = savedValue;
    if (
        // Disable the oninput filter with the user is dragging
        // the slider's knob.
        !(isMouseDown && isDragging) &&
            oldVal
    ) {
        input.value = (newVal > oldVal) ?
            oldVal + step : oldVal - step;
    }

    savedValue = new Number(input.value);
}

This method was pointed out by zcorpan on StackOverflow: stackoverflow.com/a/51988783/173630, I've just adapted it for my use case.

The onInput() function is the key method that limits the input. I added the mousedown, mouseup, and mousemove handlers later to track the dragging state. That makes the slider behave normally if you drag the knob.

If you're using React, this global savedValue variable can be handled by state/props, and connected to the input's value. See the RangeStepInput React component here for the details: small-angle-demo/src/RangeStepInput.jsx