移动端表单防非法字符应以input事件为唯一全场景入口,配合防抖、正则匹配合法前缀、光标保护及inputmode语义化键盘,禁用type="number",提交时仍需原生校验与服务端兜底。

移动端表单防止非法字符实时录入,核心不是“拦住”用户,而是“即时响应+干净输出”。原生属性(如 type="number"、pattern)在微信、iOS Safari、Android WebView 中表现割裂,无法可靠拦截粘贴、语音、长按输入等场景。真正稳定的做法是:用 input 事件做守门员,配合正则过滤 + 光标保护 + 值标准化。
必须监听 input 事件,且加防抖
所有输入方式——手输、粘贴、语音、自动填充、剪贴板长按——都会触发 input,它是唯一全场景覆盖的入口。但频繁校验会导致 UI 卡顿或状态错乱(比如快速输入时旧校验结果覆盖新正确值)。所以:
- 每次
input触发,先清除上一次定时器:clearTimeout(timeoutId) - 再设新定时器:
timeoutId = setTimeout(() => filterAndFormat(), 300)(200–400ms 最稳) -
timeoutId必须声明在闭包外或模块级,否则无法清除 - 若校验含异步请求(如查重),务必用
AbortController中断前序 pending 请求
用正则提取合法片段,而非简单 replace
直接 value.replace(/[^0-9.]/g, '') 会破坏用户体验:比如用户想输 12.50,中间输成 12..50,粗暴删点会导致光标跳到末尾,或留下 12.50 但实际值是 "12.50" —— 看似对,实则没处理前导零、多余小数点、负号位置等问题。
更稳妥的方式是「匹配合法前缀」:
- 金额类(支持一位小数):
const match = val.match(/^(-?\d*\.?\d{0,1})/),取match?.[1] || '' - 纯整数:
val.match(/^(-?\d*)/)?.[1] || '' - 手机号(11位数字):
val.match(/^1[3-9]\d{9}/)?.[0] || ''
匹配后赋值给 input.value,再手动恢复光标位置(需记录原始 selectionStart/End 并修正)。
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
type="text" + inputmode 是最稳的组合
别迷信 type="number":微信 Android 常回退全键盘;iOS 不显示小数点;pattern 对它完全无效;valueAsNumber 遇非法值直接 NaN,无法溯源。
推荐写法:
-
<input type="text" inputmode="decimal">(金额) -
<input type="text" inputmode="numeric">(整数) -
inputmode是 W3C 标准,在主流 WebView(含微信 X5)中兼容良好,能稳定唤起带小数点/退格键的数字键盘 - 语义降级保底:即使
inputmode失效,type="text"仍可由 JS 完全接管
提交前仍要走原生校验链
JS 过滤保证了 input.value 干净,但不能替代表单级约束。提交时仍需:
- 调用
input.checkValidity()检查required、min/max等原生规则 - 若失败,调用
input.reportValidity()弹出浏览器默认提示(注意:不要在input里调,会打断输入) - 同时用
setCustomValidity("")清空上次错误,再根据当前值手动设:input.setCustomValidity(isValid ? "" : "请输入有效金额") - 服务端必须二次校验——前端一切防护都可绕过










