约 6 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
type="number" 的设计目标是「浏览器辅助输入数值」,不是输入校验:它允许 e/E 和 +/-,滚轮会改值,maxlength 失效,取到的 value 仍是字符串。要做真正的数字过滤,用 type="text"(配 inputmode)+ JS 过滤。
这些行为都是规范规定的,改不掉,只能绕开。
| type="number" 的行为 | 带来的问题 |
|---|---|
| 允许输入 e / E(科学计数法合法) | 用户看到「能输入 e」,校验逻辑容易漏判 |
| 允许 + 和 - 前缀 | 正负号混进业务字段(如手机号) |
| 滚轮悬停会增减数值 | 填表时误滚一下,数据悄悄变了 |
| maxlength 不生效 | 想限 11 位手机号做不到,得用 JS |
| step 不匹配时表单校验静默失败 | value=1.5 配 step=1 直接 invalid,无提示 |
| 粘贴非数字有时被静默清空 | 用户以为输入了,实际 value 为空 |
监听 input 而不是 keydown:keydown 拦不住粘贴、拖拽和输入法,input 事件在值变化后触发,处理的是最终结果。
const input = document.querySelector('#qty');
input.addEventListener('input', () => {
// 把所有非数字字符替换为空
const cleaned = input.value.replace(/\D/g, '');
if (cleaned !== input.value) input.value = cleaned;
});中文输入法在「组词」过程中会反复触发 input 事件,事件里带着拼音中间态;如果在此时过滤,可能出现删字错乱、候选词被吞。正确做法:compositionstart 到 compositionend 之间不动值,end 之后再统一清理。
let composing = false;
input.addEventListener('compositionstart', () => composing = true);
input.addEventListener('compositionend', () => {
composing = false;
clean(); // 组词结束后再做一次清理
});
input.addEventListener('input', () => { if (!composing) clean(); });
function clean() {
const cleaned = input.value.replace(/\D/g, '');
if (cleaned !== input.value) input.value = cleaned;
}允许一个小数点比看起来麻烦:要防连续两个点、防点在开头、防清空后只剩「.」。
function bindNumberOnly(input, { decimal = false } = {}) {
const pattern = decimal ? /[^\d.]/g : /\D/g;
let composing = false;
const clean = () => {
let v = input.value.replace(pattern, '');
if (decimal) {
const i = v.indexOf('.');
if (i !== -1) v = v.slice(0, i + 1) + v.slice(i + 1).replace(/\./g, '');
if (v === '.') v = '';
else if (v.startsWith('.')) v = '0' + v;
}
if (v !== input.value) input.value = v;
};
input.addEventListener('compositionstart', () => composing = true);
input.addEventListener('compositionend', () => { composing = false; clean(); });
input.addEventListener('input', () => { if (!composing) clean(); });
input.addEventListener('blur', clean); // 失焦兜底
}
// 用法
bindNumberOnly(document.querySelector('#phone')); // 纯整数
bindNumberOnly(document.querySelector('#price'), { decimal: true }); // 允许小数功能跑通之后,线上问题往往出在这些边角。
paste 事件里可以拿到剪贴板纯文本,提前清洗再写回;HTML 侧用 inputmode 引导移动端键盘类型。
<!-- type 保持 text,inputmode 引导移动端数字键盘 --> <input type="text" id="price" inputmode="decimal" maxlength="12">
input.addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.clipboardData || window.clipboardData).getData('text');
// 粘贴内容统一走同一套清洗逻辑
input.value = text.replace(/[^\d.]/g, '');
input.dispatchEvent(new Event('input'));
});把 <input type="number"> 改成 <input type="text" inputmode="decimal">,滚轮改值、e 字符、maxlength 失效三个问题同时消失;再接上面的过滤函数兜底。正则写不准时,先在正则测试工具里跑一遍 /[^\d.]/g 和边界用例,正则速查表里有完整的数字校验表达式。
HTML 规范允许 number 输入浮点数语法,含科学计数法的 e/E,这是特性不是 bug。需要严格数字过滤请改用 type="text" 加 JS。
组词过程中 input 事件携带拼音中间态,直接改 value 会破坏候选词。需用 compositionstart/end 标记组词期间跳过过滤,end 后统一清理。
不能。它只影响移动端虚拟键盘的布局,桌面浏览器仍可输入任意字符,必须与 JS 过滤配合使用。
可以监听 wheel 事件并 preventDefault,但更彻底的办法是改用 type="text",问题根源就不存在了。