Skip to content

number 数字输入

type="number" 创建一个专门用于数字输入的控件,提供上下箭头(spinners)用于增减数值,并支持 minmaxstep 等数字特有的属性进行范围和精度控制。本节将详细讲解数字输入控件的使用方法、spinners 按钮的行为、以及处理无效输入的策略。

前置知识

阅读本节前,建议先了解:email 邮箱输入

基础概念

什么是 number 输入

type="number" 创建一个数字输入框,它具有以下特征:

  1. Spinners 按钮:右侧有上下箭头按钮,点击可增减数值
  2. 输入过滤:部分浏览器会阻止非数字字符的输入
  3. 自动验证:提交时检查是否为有效数字,以及是否在 min/max 范围内
  4. 移动端键盘:在手机上弹出纯数字键盘
html
<!-- 基本数字输入 -->
<input type="number" name="age" min="0" max="150" step="1">

<!-- 带步进和范围 -->
<input type="number" name="price" min="0" max="9999" step="0.01" value="9.99">

number 的核心特征

特征说明
输入方式键盘 + spinners 按钮
数据类型浮点数(提交的值始终是字符串形式的数字)
自动验证是(数字格式 + min/max 范围)
移动端键盘数字键盘(含小数点和正负号)
spinners有(可通过 CSS 隐藏)

语法

基本语法

html
<input
  type="number"
  name="quantity"
  id="quantity"
  value="1"
  min="0"
  max="100"
  step="1"
  placeholder="请输入数量"
  required
  readonly
  disabled
>

数字专用属性

属性值类型默认值说明
min数字无限制允许的最小值
max数字无限制允许的最大值
step数字1步进值(点击箭头时的增减量)
value数字空字符串初始值
placeholder字符串空值时的提示文字

详细说明

min、max、step 属性

这三个属性控制数字的范围和精度:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>min max step</title>
  <style>
    .demo { margin-bottom: 20px; padding: 12px; border: 1px solid #e0e0e0; border-radius: 8px; }
    label { display: block; font-weight: 500; margin-bottom: 6px; }
    input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
  </style>
</head>
<body>
  <h2>min、max、step 演示</h2>

  <!-- 年龄:0~150,步进1 -->
  <div class="demo">
    <label for="age">年龄(0~150,步进1):</label>
    <input type="number" id="age" name="age" min="0" max="150" step="1" value="25">
    <p style="color: #999; font-size: 13px;">
      点击上箭头 +1,下箭头 -1。不能超过 150 或低于 0。
    </p>
  </div>

  <!-- 价格:0~9999.99,步进0.01 -->
  <div class="demo">
    <label for="price">价格(0~9999.99,步进0.01):</label>
    <input type="number" id="price" name="price"
           min="0" max="9999.99" step="0.01" value="19.99">
    <p style="color: #999; font-size: 13px;">
      点击箭头每次变化 0.01(一分)。保留两位小数。
    </p>
  </div>

  <!-- 评分:1~5,步进0.5(半星) -->
  <div class="demo">
    <label for="rating">评分(1~5,步进0.5):</label>
    <input type="number" id="rating" name="rating"
           min="1" max="5" step="0.5" value="3.5">
    <p style="color: #999; font-size: 13px;">
      值只能是 1.0, 1.5, 2.0, 2.5, ... 5.0
    </p>
  </div>

  <!-- 偶数:step="2" -->
  <div class="demo">
    <label for="even">偶数(2, 4, 6, 8...):</label>
    <input type="number" id="even" name="even" min="2" step="2" value="10">
    <p style="color: #999; font-size: 13px;">
      step=2 确保值总是偶数
    </p>
  </div>
</body>
</html>

step 的特殊值

step 属性支持一些特殊值:

step 值含义示例
1整数(默认值)1, 2, 3, 4...
0.01两位小数1.00, 1.01, 1.02...
0.1一位小数1.0, 1.1, 1.2...
0.5半数1.0, 1.5, 2.0...
any任意精度(无步进限制)1.234567...
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>step 特殊值</title>
</head>
<body>
  <h2>step="any" 允许任意精度</h2>
  <form>
    <!--
      step="any" 取消步进限制
      用户可以输入任意精度的数字
      但 spinners 箭头默认步进为 1
    -->
    <label for="latitude">纬度:</label>
    <input type="number" id="latitude" name="lat"
           min="-90" max="90" step="any"
           placeholder="如 39.9042">

    <br><br>

    <!--
      step="0" 是无效值,等同于 step="any"
    -->
    <label for="longitude">经度:</label>
    <input type="number" id="longitude" name="lng"
           min="-180" max="180" step="0"
           placeholder="如 116.4074">

    <br><br>
    <button type="submit">提交</button>
  </form>
</body>
</html>

step 与 value 的对齐

如果 value 或用户输入的值不符合 step 的间隔,表单提交时浏览器会报告验证错误。例如 step="2" min="0",输入 3 会导致验证失败,因为 3 不是 2 的倍数。

spinners 按钮

spinners 是数字输入框右侧的上下箭头按钮,用于快速增减数值。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>spinners 按钮</title>
  <style>
    .demo { margin-bottom: 20px; padding: 16px; border: 1px solid #e0e0e0; border-radius: 8px; }

    /* 隐藏 spinners 按钮(Webkit 浏览器) */
    .no-spinners::-webkit-inner-spin-button,
    .no-spinners::-webkit-outer-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }

    /* 隐藏 spinners 按钮(Firefox) */
    .no-spinners {
      -moz-appearance: textfield;
    }

    /* 自定义 spinners 按钮 */
    .custom-number {
      display: flex;
      align-items: center;
      gap: 0;
      width: fit-content;
    }

    .custom-number input {
      width: 80px;
      text-align: center;
      padding: 8px;
      border: 1px solid #ccc;
      border-radius: 0;
      font-size: 16px;
    }

    .custom-number input:focus {
      outline: none;
      border-color: #4a90d9;
    }

    .custom-number button {
      width: 36px;
      height: 38px;
      border: 1px solid #ccc;
      background-color: #f5f5f5;
      cursor: pointer;
      font-size: 18px;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .custom-number button:hover {
      background-color: #e0e0e0;
    }

    .custom-number button:first-child {
      border-radius: 4px 0 0 4px;
    }

    .custom-number button:last-child {
      border-radius: 0 4px 4px 0;
    }
  </style>
</head>
<body>
  <h2>spinners 按钮控制</h2>

  <!-- 默认 spinners -->
  <div class="demo">
    <h3>默认 spinners</h3>
    <label for="default">数量:</label>
    <input type="number" id="default" name="quantity" value="5" min="0" max="100">
  </div>

  <!-- 隐藏 spinners -->
  <div class="demo">
    <h3>隐藏 spinners(纯键盘输入)</h3>
    <label for="hidden">数量:</label>
    <input type="number" id="hidden" name="quantity" value="5" class="no-spinners">
  </div>

  <!-- 自定义 spinners -->
  <div class="demo">
    <h3>自定义 spinners 样式</h3>
    <div class="custom-number">
      <button type="button" onclick="changeValue('custom', -1)">-</button>
      <input type="number" id="custom" name="quantity" value="1" min="1" max="99" class="no-spinners">
      <button type="button" onclick="changeValue('custom', 1)">+</button>
    </div>
  </div>

  <script>
    function changeValue(inputId, delta) {
      const input = document.getElementById(inputId);
      const min = parseFloat(input.min) || -Infinity;
      const max = parseFloat(input.max) || Infinity;
      let newVal = parseFloat(input.value) + delta;

      // 限制在 min/max 范围内
      newVal = Math.max(min, Math.min(max, newVal));
      input.value = newVal;
    }
  </script>
</body>
</html>

无效输入处理

当用户在 type="number" 输入框中输入非数字内容时,不同浏览器行为不同:

场景Chrome 行为Firefox 行为提交时的值
输入字母部分阻止输入允许输入,但标记为无效空字符串
输入特殊字符阻止输入允许输入空字符串
清空内容允许允许空字符串
输入 3.5(step=2)允许输入,验证失败允许输入,验证失败验证失败不提交
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>无效输入处理</title>
</head>
<body>
  <h2>理解无效输入</h2>
  <form id="numberForm">
    <label for="amount">金额:</label>
    <input type="number" id="amount" name="amount" min="0" step="0.01">

    <button type="submit">提交</button>
  </form>

  <div id="debug" style="margin-top: 20px; padding: 12px; background: #f5f5f5; border-radius: 8px;">
    <p>输入值:<span id="rawValue">-</span></p>
    <p>valueAsNumber:<span id="numValue">-</span></p>
    <p>valid.valid:<span id="isValid">-</span></p>
  </div>

  <script>
    const input = document.getElementById('amount');
    const rawEl = document.getElementById('rawValue');
    const numEl = document.getElementById('numValue');
    const validEl = document.getElementById('isValid');

    // 实时监控输入值
    input.addEventListener('input', function() {
      // value 属性:返回字符串
      rawEl.textContent = `"${this.value}"`;

      // valueAsNumber:返回数字或 NaN
      const num = this.valueAsNumber;
      numEl.textContent = Number.isNaN(num) ? 'NaN(无效数字)' : num;

      // 有效性检查
      validEl.textContent = this.validity.valid ? '有效' : '无效';
    });
  </script>
</body>
</html>

空值与无效值的区别

  • 空输入框:value 为空字符串 ""valueAsNumberNaNvalidity.valueMissingtrue(如果设置了 required)
  • 无效输入(如字母):value 为空字符串 ""(浏览器清除了无效内容),valueAsNumberNaNvalidity.badInputtrue

valueAsNumber 属性

HTMLInputElement.valueAsNumber 属性以数字类型返回输入框的值,比 parseFloat(input.value) 更可靠:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>valueAsNumber</title>
</head>
<body>
  <form id="calcForm">
    <label for="num1">数字 1:</label>
    <input type="number" id="num1" name="num1" value="10">

    <label for="num2">数字 2:</label>
    <input type="number" id="num2" name="num2" value="3">

    <button type="button" id="addBtn">相加</button>
  </form>

  <p id="result"></p>

  <script>
    document.getElementById('addBtn').addEventListener('click', function() {
      const num1 = document.getElementById('num1').valueAsNumber;
      const num2 = document.getElementById('num2').valueAsNumber;

      // 检查是否为有效数字
      if (Number.isNaN(num1) || Number.isNaN(num2)) {
        document.getElementById('result').textContent = '请输入有效的数字';
        return;
      }

      const sum = num1 + num2;
      document.getElementById('result').textContent = `${num1} + ${num2} = ${sum}`;
    });
  </script>
</body>
</html>

实战示例

商品数量选择器

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>数量选择器</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      max-width: 600px;
      margin: 40px auto;
      padding: 20px;
    }

    .product {
      display: flex;
      align-items: center;
      padding: 20px;
      border: 1px solid #e0e0e0;
      border-radius: 8px;
      margin-bottom: 16px;
    }

    .product-info {
      flex: 1;
    }

    .product-name {
      font-size: 16px;
      font-weight: 500;
    }

    .product-price {
      color: #e74c3c;
      font-size: 18px;
      margin-top: 4px;
    }

    /* 隐藏原生 spinners */
    .qty-input::-webkit-inner-spin-button,
    .qty-input::-webkit-outer-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }

    .qty-input {
      -moz-appearance: textfield;
    }

    .qty-control {
      display: flex;
      align-items: center;
      border: 1px solid #ddd;
      border-radius: 6px;
      overflow: hidden;
    }

    .qty-btn {
      width: 36px;
      height: 36px;
      border: none;
      background-color: #f5f5f5;
      cursor: pointer;
      font-size: 18px;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .qty-btn:hover {
      background-color: #e0e0e0;
    }

    .qty-btn:disabled {
      color: #ccc;
      cursor: not-allowed;
    }

    .qty-input {
      width: 50px;
      text-align: center;
      border: none;
      border-left: 1px solid #ddd;
      border-right: 1px solid #ddd;
      padding: 8px;
      font-size: 14px;
    }

    .qty-input:focus {
      outline: none;
    }

    .subtotal {
      text-align: right;
      padding: 16px;
      font-size: 18px;
    }

    .subtotal strong {
      color: #e74c3c;
    }
  </style>
</head>
<body>
  <h2>购物车</h2>

  <div class="product">
    <div class="product-info">
      <div class="product-name">HTML5 权威指南</div>
      <div class="product-price" data-price="89.00">¥89.00</div>
    </div>
    <div class="qty-control">
      <button type="button" class="qty-btn" data-action="decrease">-</button>
      <input type="number" class="qty-input" value="1" min="1" max="99" data-price="89.00">
      <button type="button" class="qty-btn" data-action="increase">+</button>
    </div>
  </div>

  <div class="product">
    <div class="product-info">
      <div class="product-name">CSS3 实战手册</div>
      <div class="product-price" data-price="69.00">¥69.00</div>
    </div>
    <div class="qty-control">
      <button type="button" class="qty-btn" data-action="decrease">-</button>
      <input type="number" class="qty-input" value="1" min="1" max="99" data-price="69.00">
      <button type="button" class="qty-btn" data-action="increase">+</button>
    </div>
  </div>

  <div class="subtotal">
    合计:<strong id="total">¥158.00</strong>
  </div>

  <script>
    const qtyControls = document.querySelectorAll('.qty-control');

    qtyControls.forEach(control => {
      const input = control.querySelector('.qty-input');
      const decreaseBtn = control.querySelector('[data-action="decrease"]');
      const increaseBtn = control.querySelector('[data-action="increase"]');

      // 更新按钮状态
      function updateButtons() {
        decreaseBtn.disabled = parseInt(input.value) <= parseInt(input.min);
        increaseBtn.disabled = parseInt(input.value) >= parseInt(input.max);
      }

      // 点击加减按钮
      decreaseBtn.addEventListener('click', () => {
        if (parseInt(input.value) > parseInt(input.min)) {
          input.value = parseInt(input.value) - 1;
          updateButtons();
          updateTotal();
        }
      });

      increaseBtn.addEventListener('click', () => {
        if (parseInt(input.value) < parseInt(input.max)) {
          input.value = parseInt(input.value) + 1;
          updateButtons();
          updateTotal();
        }
      });

      // 手动输入时验证
      input.addEventListener('change', () => {
        let val = parseInt(input.value);
        if (isNaN(val) || val < parseInt(input.min)) val = parseInt(input.min);
        if (val > parseInt(input.max)) val = parseInt(input.max);
        input.value = val;
        updateButtons();
        updateTotal();
      });

      updateButtons();
    });

    // 计算总价
    function updateTotal() {
      let total = 0;
      document.querySelectorAll('.qty-input').forEach(input => {
        const qty = parseInt(input.value) || 0;
        const price = parseFloat(input.dataset.price);
        total += qty * price;
      });
      document.getElementById('total').textContent = '¥' + total.toFixed(2);
    }
  </script>
</body>
</html>

注意事项

  1. number 不是整数类型type="number" 支持浮点数,不是只能输入整数。如果需要限制为整数,使用 step="1"

  2. 空值提交为空字符串:当输入框为空时,提交的值是空字符串 "",而不是 "0"。服务器端需要处理这种情况。

  3. 浮点数精度问题0.1 + 0.2 的结果不是精确的 0.3(JavaScript 浮点数精度问题)。涉及金额计算时,应使用整数(分)或专门的数学库。

  4. 不要用 number 收集非数字输入:如果用户可能需要输入带单位的值(如 "100px"、"10kg"),应使用 type="text" 配合 pattern 验证。

  5. spinners 的样式控制能力有限:浏览器对 spinners 的样式控制能力很弱,如果需要自定义外观,建议隐藏原生 spinners,用 JavaScript 实现自定义按钮。

最佳实践

  1. 始终设置 min 和 step:即使是可选的数字输入,设置合理的 min(如 min="0")和 step 可以防止意外输入。

  2. 金额使用 step="0.01":货币计算通常精确到分,使用 step="0.01" 确保步进正确。

  3. 使用 valueAsNumber 获取数字值:在 JavaScript 中使用 input.valueAsNumber 获取数值,而非 parseFloat(input.value),后者对空值返回 0 而非 NaN。

  4. 考虑隐藏 spinners:在移动端或需要自定义外观时,隐藏原生 spinners 并实现自定义按钮。

  5. 处理 NaN:在 JavaScript 中处理 valueAsNumber 时,始终检查 Number.isNaN(),防止无效输入导致计算错误。

下一节

继续学习:tel 电话输入

参考链接