身份证号应使用文本输入而非 number 类型,因其含前导零和末位X;需自定义键盘控制输入流,校验长度与字符合法性,并用textContent更新显示。

为什么不能直接用 input[type="number"] 输身份证号
因为身份证号是字符串,不是数值——开头可能为 0(如北京 110101…),末位可能是 X(罗马数字 10),input[type="number"] 会自动过滤前导零、拒绝 X、还可能触发移动端数字键盘但无法输入字母,导致用户输错或输不全。
真正要的是「带中文字符提示 + 键盘约束 + 防误触」的自定义键盘,不是改 type 属性能解决的。
- 移动端 iOS/Android 对
inputmode支持不一致,inputmode="text"最稳妥 - 必须禁止粘贴非身份证格式内容(比如带空格、换行、中文括号)
- 末位 X 应支持大小写输入,且光标不能跳到末尾后强制覆盖——这是常见交互 bug
用 div 模拟键盘 + JavaScript 控制输入逻辑
放弃原生 input 的键盘行为,用 contenteditable="false" 的 div 或禁用状态的 input 显示值,再盖一层按钮键盘。所有按键由 JS 拦截并拼接字符串。
关键点不在“画键盘”,而在“控制输入流”:
立即学习“前端免费学习笔记(深入)”;
- 监听键盘事件时,优先用
event.key(不是event.code),它反映实际输入字符,能区分大小写 X 和小写 x - 每次输入前校验长度:前 17 位只允许数字,第 18 位只允许数字或
'X'/'x' - 用
element.textContent = newValue而非.value更新显示(避免 focus 失去或光标重置) - 删除键逻辑要单独处理:
Backspace删除末位,但若当前为空,需防止删到上一个字段(如有分段输入场景)
document.getElementById('idcard-keyboard').addEventListener('click', function(e) {
if (e.target.matches('button[data-key]')) {
const key = e.target.dataset.key;
const inputEl = document.getElementById('idcard-display');
let val = inputEl.textContent;
if (val.length < 17 && /\d/.test(key)) {
inputEl.textContent = val + key;
} else if (val.length === 17 && /[Xx\d]/.test(key)) {
inputEl.textContent = val + key.toUpperCase();
}
}
});
移动端软键盘适配与 focus 陷阱
安卓微信、iOS Safari 在 contenteditable=false 元素上不唤起软键盘;而给 input 加 readonly 又会导致部分机型仍可长按粘贴。最稳方案是:保留一个隐藏的 input[type="text"] 用于聚焦唤起键盘,但监听其 input 事件后立即清空,并把值同步到显示区。
- 隐藏 input 必须设
position: absolute; left: -9999px;,不能用display: none或visibility: hidden(否则 iOS 不触发键盘) - 调用
hiddenInput.focus()后,立刻hiddenInput.select()并document.execCommand('delete')清空(兼容旧版) - 监听
hiddenInput的input事件时,只取event.inputType === 'insertText'的变更,忽略格式化插入 - 注意 iOS 16.4+ 对
execCommand的限制,建议 fallback 到setSelectionRange(0,0)+replaceRange操作
校验逻辑不能只靠前端正则
前端正则 /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ 只能筛基本格式,无法判断:出生年月是否真实存在(如 20250230)、区域码是否有效、校验码是否匹配。
真正在意数据质量的场景,必须在用户完成输入后(比如失焦或点击提交),用 JS 做完整校验:
- 提取前 17 位,按国标 GB11643-1999 计算加权和与模 11,比对第 18 位是否符合
['1','0','X','9','8','7','6','5','4','3','2']序列 - 日期部分用
new Date(year, month-1, day)检查是否合法(注意月份从 0 开始) - 区域码可内置精简版对照表(约 300 条),不必请求后端——但别用网上随便抄的过期列表(如已撤销的 130100)
校验函数名建议叫 validateIdCard,别叫 checkID——后者容易被当成简单正则封装。
最常被忽略的一点:用户可能快速连点键盘按钮,或在输入中途切换 App,导致状态不同步。所有更新操作应加防抖(setTimeout + clearTimeout),且每次更新后手动同步光标位置到末尾(el.setSelectionRange(val.length, val.length)),否则下一次输入会插在中间。



















