number 数字输入
type="number" 创建一个专门用于数字输入的控件,提供上下箭头(spinners)用于增减数值,并支持 min、max、step 等数字特有的属性进行范围和精度控制。本节将详细讲解数字输入控件的使用方法、spinners 按钮的行为、以及处理无效输入的策略。
前置知识
阅读本节前,建议先了解:email 邮箱输入
基础概念
什么是 number 输入
type="number" 创建一个数字输入框,它具有以下特征:
- Spinners 按钮:右侧有上下箭头按钮,点击可增减数值
- 输入过滤:部分浏览器会阻止非数字字符的输入
- 自动验证:提交时检查是否为有效数字,以及是否在 min/max 范围内
- 移动端键盘:在手机上弹出纯数字键盘
<!-- 基本数字输入 -->
<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 隐藏) |
语法
基本语法
<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 属性
这三个属性控制数字的范围和精度:
<!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... |
<!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 是数字输入框右侧的上下箭头按钮,用于快速增减数值。
<!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) | 允许输入,验证失败 | 允许输入,验证失败 | 验证失败不提交 |
<!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为空字符串"",valueAsNumber为NaN,validity.valueMissing为true(如果设置了 required) - 无效输入(如字母):
value为空字符串""(浏览器清除了无效内容),valueAsNumber为NaN,validity.badInput为true
valueAsNumber 属性
HTMLInputElement.valueAsNumber 属性以数字类型返回输入框的值,比 parseFloat(input.value) 更可靠:
<!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>实战示例
商品数量选择器
<!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>注意事项
number 不是整数类型:
type="number"支持浮点数,不是只能输入整数。如果需要限制为整数,使用step="1"。空值提交为空字符串:当输入框为空时,提交的值是空字符串
"",而不是"0"。服务器端需要处理这种情况。浮点数精度问题:
0.1 + 0.2的结果不是精确的0.3(JavaScript 浮点数精度问题)。涉及金额计算时,应使用整数(分)或专门的数学库。不要用 number 收集非数字输入:如果用户可能需要输入带单位的值(如 "100px"、"10kg"),应使用
type="text"配合pattern验证。spinners 的样式控制能力有限:浏览器对 spinners 的样式控制能力很弱,如果需要自定义外观,建议隐藏原生 spinners,用 JavaScript 实现自定义按钮。
最佳实践
始终设置 min 和 step:即使是可选的数字输入,设置合理的
min(如min="0")和step可以防止意外输入。金额使用 step="0.01":货币计算通常精确到分,使用
step="0.01"确保步进正确。使用 valueAsNumber 获取数字值:在 JavaScript 中使用
input.valueAsNumber获取数值,而非parseFloat(input.value),后者对空值返回 0 而非 NaN。考虑隐藏 spinners:在移动端或需要自定义外观时,隐藏原生 spinners 并实现自定义按钮。
处理 NaN:在 JavaScript 中处理
valueAsNumber时,始终检查Number.isNaN(),防止无效输入导致计算错误。
下一节
继续学习:tel 电话输入