银行卡校验必须组合实时格式化、Luhn算法校验和严格剥离空格后送后端:oninput中清洗非数字字符、截断至16位、分组加空格并修复光标;Luhn独立校验纯数字串;禁用type="number",改用inputmode="numeric"且设maxlength="19"。

银行卡卡号格式校验不能只靠 pattern 或 type="number",必须组合三件事:实时格式化 + Luhn 算法校验 + 严格剥离空格后送后端。否则用户输到第 5 位就报错、粘贴失效、iOS 光标乱跳、安卓软键盘崩掉,都是常态。
为什么 pattern="\d{4} \d{4} \d{4} \d{4}" 不行
这个正则看着对,但实际完全不可用:
-
pattern只在表单提交时触发,input过程中不生效,用户无法获得实时反馈 - 它会把 “1234567890123456” 当作非法(缺空格),但用户刚敲完第 4 位时根本还没输够,不该立刻标红
- 一旦用户粘贴 “1234-5678-9012-3456” 或带中文空格,整个匹配直接失效,且浏览器不会提示具体哪错
-
pattern的错误文案无法细粒度控制,比如分不清是“长度不够”还是“Luhn 失败”
oninput 里必须做三件事:清洗、截断、分组
监听 oninput 是唯一能覆盖粘贴、拖拽、语音输入的事件。每次触发都要立刻处理:
- 先用
.replace(/\D/g, '')干掉所有非数字字符——中文数字、全角空格、短横线、字母全清,不留隐患 - 立刻
.slice(0, 16)截断,卡死上限;别等输完再提示,用户输到第 17 位时,第 17 个字符必须被丢弃 - 分组用
v.match(/.{4}/g) || [],比 for 循环拼接安全,空字符串也不报错;再.join(' ')加空格 - 关键:不能直接赋值
el.value = ...,否则安卓微信光标跳到末尾;得手动用setSelectionRange修复位置
Luhn 校验必须独立于格式化逻辑
格式化只管“看起来像卡号”,Luhn 才管“是不是真卡号”。二者必须解耦:
立即学习“前端免费学习笔记(深入)”;
- Luhn 函数输入必须是纯数字字符串,如
"4532015112830366",不能含空格或横线 - 校验前再执行一次
.replace(/\D/g, ''),防止用户绕过前端格式化直接改 DOM - 用
el.setCustomValidity()设置错误信息,比如"卡号不符合 Luhn 校验",这样checkValidity()和原生提交拦截才生效 - 不要在
oninput里每键都跑 Luhn——性能差且没必要;建议在blur或提交前校验一次即可
inputmode="numeric" 是底线,type="number" 是坑
移动端软键盘行为差异极大,选错就废:
-
type="number"在多数安卓机型上禁止输入空格,导致分隔符根本加不进去,格式化直接瘫痪 -
inputmode="numeric"只控制键盘类型,不限制字符输入,空格、横线都能打,是目前最稳的选择 - 必须设
maxlength="19"(16 位数字 + 3 个空格),多一位可能触发浏览器自动纠错,把 “1234 5678 9012 34567” 改成 “1234 5678 9012 3456” 而不通知用户 - 真实卡号严禁出现在 DOM 中:别存进
data-card,更别放value或innerHTML;后端需要原始号,应从格式化后的字符串里.replace(/\s/g, '')临时提取
最易被忽略的点:光标修复逻辑不是“锦上添花”,而是 iOS 和部分安卓 WebView 的刚需。没它,用户在中间修改一个数字,光标就会跳到末尾,反复重输——这不是体验问题,是功能残缺。



















