众所周知,HTML5的input 新增了很多类型,如 number
,email
,url
,date
系列,search
,tel
等等。这些新类型提供了更好的输入控制和验证。然而,在使用的过程中你会发现,不该对它们期望过高。其中的 number
类型比较常用,最近项目里也用到了它,过程中碰到不少麻烦,算是用hack的方式解决了,特记此文,希望对大家有所帮助。
产品的需求是,输入框只能输入数字和小数点,并且整数位数不超过9,小数位数不超过2。通常首先想到的是用<input type="number"/>
,因为语义上它是数字输入框,而且在移动端获得焦点时默认弹出数字键盘。看上去很美好,但这跟需求还差不少距离。
- 没有长度限制
- 它可以输入除数字和点号以外的字符
关于长度限制,你可能想到了maxlength
属性,但遗憾的是它对 number
类型无效。况且即便能用maxlength
,也很难实现前面的需求,因为小数部分是可选的。至于字符限制,你可能会想到用键盘事件来拦截。没错,可以监听keydown
事件,判断按键的keyCode
来拦截无效字符:
var input = document.querySelector('input');
var validKeys = [];//合法的按键列表
input.addEventListener('keydown', function(e) {
if(validKeys.indexOf(e.keyCode) < 0) {
e.preventDefault();
}
});
这样就无法输入非法字符了。然而现实是残酷的,PC上可能工作良好,但移动端浏览器对键盘事件支持并不是很好,Android webview 里取到的keyCode
全都是0。这是chrome早几年的bug,到现在都没完全解决。参考这里(需翻墙)。是不是很心塞?
别灰心,还有其他办法。比如,监听input
事件,在其value
变化的时候检测字符串。
var input = document.querySelector('input');
var validKeys = [];//合法的按键列表
input.addEventListener('input', function(e) {
if(e.target.value === '') {
input.value = oldValue;//伪代码,重置为上次有效值
}
});
<input type="number" />
有个特性,只要输入框内的字符串不能转换成浮点数,它的值就变为空了。我们可以利用这个特性,发现输入非法,就将输入值撤回。但是问题又来了,当你按退格键删除字符时,由于前面的逻辑,最后一个字符无法删除。这是因为无法区分是退格键还是输入了非法字符导致值为空,都重置为上次有效值了。结合键盘事件可以区分,但是前面说了,移动端不靠谱。既然number input 有诸多限制,为什么不直接用 text input呢?没错,text input可以避免这个问题,但是它的默认键盘不是数字键盘。还有个选择,用<input type="tel" />
,它有text input的所有特性,还能拥有默认数字键盘。燃鹅,在iOS里是拨号键盘,没有点号。
我只是想输个数字,咋这么多坑?!
稍安勿躁,终极大招来了。咱们还是继续用number input。它还有个不常用的事件,叫textInput
。这是DOM LEVEL3 的事件,但实际上w3c标准里已经删掉它了,不过验证发现chrome和safari都支持。它可以监听到任何输入,这样就可以区分是输入了非法字符还是按了退格键。
var input = document.querySelector('input');
var validKeys = [];//合法的按键列表
input.addEventListener('textInput', function(e) {
if(isNaN(e.data)) {
input.value = oldValue;//伪代码,重置为上次有效值
}
});
至此,基本解决了需求里的所有问题。
注意,以上方案只是为了在移动端webview里解决问题,并且是hack的方式,并非完全可靠。代码也省略了很多细节。如果大家有更好的办法,欢迎交流。
参考资料:
https://bugs.chromium.org/p/chromium/issues/detail?id=118639#c85
https://www.w3.org/TR/DOM-Level-3-Events/#beforeinput
https://www.filamentgroup.com/lab/type-number.html