1

I have an HTML <input> element with type="number":

Number Input

I want to use a step value of 1. That is, any integer is allowed. However, I would also like the arrow buttons to increment/decrement the value of the input by some value, for example, 5. Is this possible?

From the MDN docs, I can't find a way to accomplish this using HTML alone. I would also accept an answer involving intercepting the input event using javascript to change the value of the input, but I don't know of a way to distinguish between a change due to keypresses vs. a change due to clicking the arrow buttons.

R. Odd
  • 21
  • 1

3 Answers3

0

You can always capture the input in javascript and convert the values to an integer with parseInt and use the normal step to control the arrow stepping.

With this method you don't have to worry about distinguishing between the stepping and input as the steps will already be integers anyway.

let nums = document.querySelectorAll("input[type='number']");

nums.forEach(function(e){
  e.addEventListener("input",function(el){
    el.target.value = parseInt(el.target.value)    
  });
});
<input type="number" step="4" />
imvain2
  • 15,480
  • 1
  • 16
  • 21
  • 1
    This doesn't answer the question. (1) I want to use a different number amount for `step` vs. the arrow buttons (2) the `input` event doesn't distinguish between the arrow buttons vs. just typing numbers in. So this solution would affect typing as well, which I don't want. – R. Odd Jun 24 '22 at 21:31
0

Currently it is not possible to capture a click on the spinners directly, but there are some ways to detect a click on them.

By using different events it is possible to adjust the step size for the moment. But it may cause some unwanted effect if the user uses the mouse to click on a spinner, keeps the mousekey pressed and presses an arrow key up/down:

// fires ones right before the value is changed for the first time
input.addEventListener("mousedown", ({ target }) => target.setAttribute("step", 5));
input.addEventListener("mouseup", ({ target }) => target.setAttribute("step", 1));
// fires after release of the mouse key, even if the user moved the mouse
input.addEventListener("change", ({ target }) => target.setAttribute("step", 1));
<input id="input" type="number" step="1" value="0" />
Christopher
  • 3,124
  • 2
  • 12
  • 29
0

<label> Overlay

Make a <label> that lays over the spinner button of the <input> and in the eventhandler use the .stepUp() and .stepDown() methods when the <label> is clicked.

In Example A, the <label> is outlined in red and the two nested <b> are outlined in blue. The outlines are just for the demo of course and can be easily removed. See Example A

XY Coordinates

Another way is to place an abspos (absolute positioned) <input> in a relpos container and change the step attribute if the click is within the xy coords of the <input> spinner button. See Example B.

Details are commented in both Example A and B

Example A

<label> overlay

// Bind <label> to the click event
document.querySelector('.spinner').onclick = incDec;

function incDec(e) {
  // Reference the tag the user clicked
  const clk = e.target;
  // Reference the <input>
  const int = this.previousElementSibling;
  // If the user clicked .inc...
  if (clk.matches('.inc')) {
    //...Increment <input> value by 5
    int.stepUp(5);
  }
  // If the user clicked .dec...
  if (clk.matches('.dec')) {
    //...Decrement <input> value by 5
    int.stepDown(5);
  }
}
.spinner {
  position: relative;
  top: -4px;
  right: 24px;
  z-index: 1;
  display: inline-flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: center;
  width: 16px;
  height: 18px;
  outline: 1px dashed red;
}

.spinner b {
  display: inline-block;
  width: 100%;
  height: 50%;
  outline: 1px blue solid
}
<input id='int' type='number' min='0' step='1'>
<label for='int' class='spinner'><b class='inc'></b><b class='dec'></b></label>

Example B

XY Coordinates

// Reference <form>
const UI = document.forms.UI;
// Reference all form controls
const IO = UI.elements;
// Reference <fieldset>
const set = IO.set;
// Reference <input>
const int = IO.int;
// Define an empty array to xy coords
let point = [];

// Bind <fieldset> to 'mousemove' event
set.onmousemove = xy;
// Bind <input> to 'mousedown' event
int.onmousedown = setPoint;
/*
Bind <input> to 'mouseup' event
restore step attribute back to 1
*/
int.onmouseup = e => e.target.step = 1;

/*
Assigns current xy coords within the
perimeter of <fieldset> to array point
*/
function xy(e) {
  let x = e.clientX;
  let y = e.clientY;
  point[0] = x;
  point[1] = y;
}

/*
Checks if the user is clicking the <input>
spinner. If user is, then step attribute
is changed to 5
*/
function setPoint(e) {
  let x = point[0];
  let y = point[1];
  if (x > 215 && x < 231 && y > 30 && y < 51) {
    this.step = 5;
  }
}
html {
  font: 300 62.5%/1 Consolas;
}

fieldset {
  position: relative;
  width: 24rem;
  height: 5rem;
}

input {
  position: absolute;
  left: 2.5rem;
  top: 1.85rem;
  width: 19rem;
  font: inherit;
  font-size: 1.6rem;
  text-align: center;
}
<form id='UI'>
  <fieldset id='set'>
    <input id='int' type='number' min='0' step='1'>
  </fieldset>
</form>
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • I also thougt of this but you have to take care of a lot more than just increment and decrement. - 1. The user is forced to click for each inc-/decrement once. 2. The input's default event listeners won't fire (like onchange or oninput). 3. It doesn't support the min/max attribute (currently) - Creating custom spinners creates a rat tail, you have to take care off. – Christopher Jun 24 '22 at 22:31