眾所周知,HTML5的input 新增了很多類型,如 number,email,url,date系列,search,tel等等。這些新類型提供了更好的輸入控制和驗(yàn)證。然而,在使用的過程中你會發(fā)現(xiàn),不該對它們期望過高。其中的 number 類型比較常用,最近項(xiàng)目里也用到了它,過程中碰到不少麻煩,算是用hack的方式解決了,特記此文,希望對大家有所幫助。
產(chǎn)品的需求是,輸入框只能輸入數(shù)字和小數(shù)點(diǎn),并且整數(shù)位數(shù)不超過9,小數(shù)位數(shù)不超過2。通常首先想到的是用<input type="number"/>,因?yàn)檎Z義上它是數(shù)字輸入框,而且在移動(dòng)端獲得焦點(diǎn)時(shí)默認(rèn)彈出數(shù)字鍵盤??瓷先ズ苊篮?,但這跟需求還差不少距離。
- 沒有長度限制
- 它可以輸入除數(shù)字和點(diǎn)號以外的字符
關(guān)于長度限制,你可能想到了maxlength屬性,但遺憾的是它對 number類型無效。況且即便能用maxlength,也很難實(shí)現(xiàn)前面的需求,因?yàn)樾?shù)部分是可選的。至于字符限制,你可能會想到用鍵盤事件來攔截。沒錯(cuò),可以監(jiān)聽keydown事件,判斷按鍵的keyCode 來攔截?zé)o效字符:
var input = document.querySelector('input');
var validKeys = [];//合法的按鍵列表
input.addEventListener('keydown', function(e) {
if(validKeys.indexOf(e.keyCode) < 0) {
e.preventDefault();
}
});
這樣就無法輸入非法字符了。然而現(xiàn)實(shí)是殘酷的,PC上可能工作良好,但移動(dòng)端瀏覽器對鍵盤事件支持并不是很好,Android webview 里取到的keyCode全都是0。這是chrome早幾年的bug,到現(xiàn)在都沒完全解決。參考這里(需翻墻)。是不是很心塞?
別灰心,還有其他辦法。比如,監(jiān)聽input事件,在其value變化的時(shí)候檢測字符串。
var input = document.querySelector('input');
var validKeys = [];//合法的按鍵列表
input.addEventListener('input', function(e) {
if(e.target.value === '') {
input.value = oldValue;//偽代碼,重置為上次有效值
}
});
<input type="number" />有個(gè)特性,只要輸入框內(nèi)的字符串不能轉(zhuǎn)換成浮點(diǎn)數(shù),它的值就變?yōu)榭樟恕N覀兛梢岳眠@個(gè)特性,發(fā)現(xiàn)輸入非法,就將輸入值撤回。但是問題又來了,當(dāng)你按退格鍵刪除字符時(shí),由于前面的邏輯,最后一個(gè)字符無法刪除。這是因?yàn)闊o法區(qū)分是退格鍵還是輸入了非法字符導(dǎo)致值為空,都重置為上次有效值了。結(jié)合鍵盤事件可以區(qū)分,但是前面說了,移動(dòng)端不靠譜。既然number input 有諸多限制,為什么不直接用 text input呢?沒錯(cuò),text input可以避免這個(gè)問題,但是它的默認(rèn)鍵盤不是數(shù)字鍵盤。還有個(gè)選擇,用<input type="tel" />,它有text input的所有特性,還能擁有默認(rèn)數(shù)字鍵盤。燃鵝,在iOS里是撥號鍵盤,沒有點(diǎn)號。

我只是想輸個(gè)數(shù)字,咋這么多坑?!
稍安勿躁,終極大招來了。咱們還是繼續(xù)用number input。它還有個(gè)不常用的事件,叫textInput。這是DOM LEVEL3 的事件,但實(shí)際上w3c標(biāo)準(zhǔn)里已經(jīng)刪掉它了,不過驗(yàn)證發(fā)現(xiàn)chrome和safari都支持。它可以監(jiān)聽到任何輸入,這樣就可以區(qū)分是輸入了非法字符還是按了退格鍵。
var input = document.querySelector('input');
var validKeys = [];//合法的按鍵列表
input.addEventListener('textInput', function(e) {
if(isNaN(e.data)) {
input.value = oldValue;//偽代碼,重置為上次有效值
}
});
至此,基本解決了需求里的所有問題。
注意,以上方案只是為了在移動(dòng)端webview里解決問題,并且是hack的方式,并非完全可靠。代碼也省略了很多細(xì)節(jié)。如果大家有更好的辦法,歡迎交流。
參考資料:
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