爲啥要寫假鍵盤?html
仍是輸入框、光標全假的假鍵盤?前端
手機自帶的不用非得寫個假的,吃飽沒事幹吧?vue
裝逼?炫技?node
寶寶也是被逼的,寶寶也很委屈~.~android
移動端H5項目需求點:ios
進入某頁面自動彈出帶小數點的數字鍵盤,而且自帶輸入驗證,好比金額——只能輸入數字和小數點,而且只能輸入一位小數點、小數位不超過2位,且輸入前驗證不合法就不讓輸入、(UE特加功能——定製光標顏色>.<簡直是反人類的需求)。細分以下:git
(1)針對功能點1,能夠給 input 設置屬性 autofocus , 輸入框就能自動聚焦。 輕鬆搞定github
(2)針對功能點2 ,給input設置屬性 autofocus 會自動聚焦可是鍵盤並不會自動彈出;chrome
必須手動點擊輸入框鍵盤纔會彈出; 因而在進入頁面的時候用js觸發click或者foucus,發現鍵盤也不會自動彈出,延時click、focus也沒能彈出;那麼只有最後一種方案——就是讓NA端提供讓鍵盤彈出的方法。 純前端沒法搞定,須要NA端協助/,或者找PM砍掉自動彈鍵盤的需求>.<(勉強可以接受)數組
(3)針對功能點3,彈數字鍵盤的方法能夠設置 type = "number" 或者type = "tel"; 前者在Andriod能夠彈出數字鍵盤在ios端只能彈全鍵盤,後者在Android和ios彈出的都是數字鍵盤,可是!!坑爹的,彈出的數字鍵盤沒有小數點!(個人華爲榮耀9卻是很給力的給我彈了個帶小數點的數字鍵盤,不容易啊啊) 只能選擇type = "number",勉強能接受ios彈全鍵盤吧
(4)針對功能點4, 設置type = "number",發現能夠不停的輸入小數點啊啊啊啊看着真的要瘋了,第一次輸入小數點也不能自動變成'0.'
這時候聰明的你必定想到要使用事件監聽鍵入的字符,在輸入以前進行判斷,而後決定是否放入輸入框。
你確定又會開心的想到一堆可能有用的事件:onkeydown,onkeyup,onchange,oninput,onpropertychange,textInput。
路漫漫其修遠兮啊~通過不斷嘗試以後仍然發現不少問題。
(只剩下onkeyup/textInput,還有一線但願剛芭蕾>.<。)
onkeyup——其事件有兩個相關屬性event.key和event.keyCode。event.key在個人華爲榮耀9手機上都不生效(其餘低版本手機可想而知)。但其還有一個屬性event.keyCode其在PC端的值是鍵入字符的ascii碼。但在手機端輸入任何數字或者小數點其值均爲229(華爲榮耀9測試),因此onkeyup也不能用。
ontextInput——在pc和移動端都支持!!!(功夫不負有心人)其event.data能夠獲取到輸入的值。歡天喜地,舉國歡慶,啊哈哈~~
終於鬆了一口氣,只要能在輸入前獲取值就能驗證了呀。
自信滿滿的一口氣寫完驗證過程:
html
<input
id="amount-input"
autofocus
type="number"
@textInput="checkNumber"
v-model="amount"
require/>
複製代碼
js
checkNumber(event) {
var key = event.data || '';
if (key.search(/[0-9\.]/) > -1) {
var value = document.getElementById('amount-input').value;
if (key === '.' && value.search(/\./) > -1) {
event.preventDefault();
}
if (value.search(/\.\d{2}/) > -1) {
event.preventDefault();
}
} else {
event.preventDefault();
}
},
複製代碼
杯具再次發生了~~~~~我所指望的效果仍然沒有達到。
經過value獲取輸入框內全部字符失敗
發現input type = number
取到的value只能是數值,沒法獲取輸入框裏的全部字符。
也就是說若是輸入'12.',經過value獲取到是'12',只輸入'.',value獲取到的是' '空字符串,獲取不到小數點。這樣就沒法判斷是否輸入小數點,於是不能判斷是否還能輸入小數點,那就仍是能輸入無數個小數點,問題依然得不到解決。
嘗試:
(5)針對功能點5,功能4解決了,功能5是小case。。。
因此基於input + 手機自帶鍵盤實現方案要知足以上需求難以實現
如果用假鍵盤加原生input輸入框,須要作到:
禁用手機自帶鍵盤,在沒有NA暴露的方法支持的狀況下,能夠設置Input的readonly屬性。這樣的話輸入框也不能添加刪除字符了。若在能夠要NA端提供禁用手機自帶鍵盤的方法的前提下,要實現點擊假鍵盤輸入框能添加刪除字符。
如果只從後面添加刪除,很容易實現,只須要將點擊鍵盤對應的字符拼接到Input type=text
獲取到的value的後面,刪除同理。可是要是光標不在最後一位,而是在中間
圖2 光標在數字中間示例圖
複製代碼
那麼當咱們點擊假鍵盤添加或刪除字符的時候,如何能知道添加或刪除字符的位置呢。也許須要獲取光標位置。目前只有IE和火狐支持的document.selection,selectionStart能夠獲取光標位置。
// 獲取光標位置
function getCursortPosition (textDom) {
var cursorPos = 0;
if (document.selection) {
// IE Support
textDom.focus ();
var selectRange = document.selection.createRange();
selectRange.moveStart ('character', -textDom.value.length);
cursorPos = selectRange.text.length;
}else if (textDom.selectionStart || textDom.selectionStart == '0') {
// Firefox support
cursorPos = textDom.selectionStart;
}
return cursorPos;
}
複製代碼
因爲咱們的是移動端H5開發項目,考慮兼容性,顯然以上方法不能兼容大部分的機型。
以上兩種方案均難以實現,所以我只能大膽想象,要實現知足以上需求的假鍵盤就得實現假輸入框、假光標、假keyboard的一套裝備。這樣全部的元素我都能控制,上面的那些問題所有能夠解決。
雛形如果實現只能從最後面增長刪除沒有光標的假鍵盤很是容易,只須要給每一個鍵綁定一個click事件,維護一個數組,每次從後面push或者pop就能維護輸入框中的內容。
圖3 只能從最後添加、刪除且沒有光標的效果圖
複製代碼
可是這樣跟正真的輸入框效果比體驗太差了。
難點
要實現體驗跟原生鍵盤同樣而且自帶輸入驗證的假鍵盤,難點主要在於:
對於光標實現,創造一個元素設置背景色,能夠控制它隱藏和出現。
對於「點擊數字中間光標自動移過去 」,能夠每添加一個數字或者小數點就先加一個帶點擊事件的空元素space,再添加要輸入的字符。space是爲了綁定一個點擊事件,告訴光標要移動到的位置。
//字符插入,在光標前插入字符
function insert(value) {
var span = document.createElement("span"); //建立包含值的元素
span.className = 'val';
span.innerText = value;
var space = document.createElement("span");
space.className = 'space';
space.addEventListener('click', moveCursor);
var cursor = document.getElementsByClassName('cursor')[0];
inputArea.insertBefore(space, cursor);//插入空列
inputArea.insertBefore(span, cursor);//插入值
}
複製代碼
刪除時也是先刪除光標以前的數字字符,再刪除space元素。
//刪除元素
function deleteElement() {
setCursorFlash();
var cursor = document.getElementsByClassName('cursor')[0];
var n = 2; //兩個刪除動做
while(cursor.previousSibling && n > 0) {
inputArea.removeChild(cursor.previousSibling );
n--;
}
if(getInputStr().search(/^\.\d*/) > -1) {
insert(0);
}
if(getInputStr() === ''){ //元素爲空placeholder顯示
var placeHolder = document.getElementsByClassName('holder')[0];
placeHolder.className = 'holder';
}
}
複製代碼
經過chrome裏面元素審查能夠看到添加刪除的過程。
圖4 添加、刪除、光標移動元素變化圖
複製代碼
每個space元素都綁定一個click事件,用來移動光標,最右邊有個right-space能夠用來放placeholder,也能夠添加click事件,點擊時光標老是移到最後一位。
//移動光標位置
function moveCursor(event) {
var cursor = document.getElementsByClassName('cursor')[0];//獲取光標
if(event.currentTarget.className == 'right-space'){
if(!cursor.nextSibling || cursor.nextSibling.nodeName == '#text'){
return;
} else {
var ele = cursor.nextSibling;
inputArea.insertBefore(inputArea.lastElementChild, ele);
inputArea.appendChild(cursor);
}
}else {
var tempEle = event.currentTarget.nextSibling;
// var nodeName = event.currentTarget.nextSibling.nodeName;
// var cursor = document.getElementsByClassName('cursor')[0];
if(!tempEle || tempEle.nodeName == '#text') {
var temp = event.currentTarget.previousSibling;
var ele = inputArea.replaceChild( event.currentTarget, cursor);//把光標替換成當前元素
inputArea.appendChild(ele);
} else {
var temp = event.currentTarget.nextSibling;
var ele = inputArea.replaceChild( event.currentTarget, cursor);//把光標替換成當前元素
inputArea.insertBefore(ele, temp);
}
}
}
複製代碼
從上面的GIF圖能夠看出,光標始終只有一個並且有個定時任務。光標的閃動設置以下,使用原生的setInterval實現。
//設置光標定時任務
function setCursorFlash() {
//placeholder 隱藏
var placeHolder = document.getElementsByClassName('holder')[0];
placeHolder.className = 'holder hidden';
var cursor = document.getElementsByClassName('cursor')[0];
var inputContainer = document.getElementsByClassName('input-container')[0];
cursor.className = "cursor";
var isShowCursor = true;
inputContainer.focus();
showKeyBoard();
if (intervalId) {
clearInterval(intervalId);
}
intervalId = setInterval(function() {
isShowCursor = !isShowCursor;
if (isShowCursor) {
cursor.className = 'cursor';
} else {
cursor.className = 'cursor hidden';
}
}, 1000);
}
複製代碼
最終使用原生js實現的帶輸入框、光標,keyboard的假數字鍵盤。
除了完成以上功能,還實現了輸入前驗證功能,爲了跟接近真實輸入框表現,同時實現了點擊
輸入框獲取焦點、光標閃動、彈出鍵盤;失去焦點光標消失。
爲何不使用jQuery?
一是由於,當前的H5項目沒有使用jQuery。
二是由於使用VUE以後不多須要直接操做DOM,少數方法本身實現更輕量,如果只爲了使用
其一兩個方法而引入jQuery,會使得項目更重。
原生js實現效果
圖5 原生js實現輸入框、光標、鍵盤全假套件效果圖 源碼github.com/DaisyWang88…
手機掃碼驗證: sandbox.runjs.cn/show/mvjrca…(chrome插件url二維碼生成器GetCrx.cn)
因爲移動端click事件有300毫秒延時,所以原生js實現的效果,有點不是很流暢。若使用原生JS實現版的須要使fastclick或zepto的tap事件解決延時問題。
PS:以前說‘VUE自己解決300毫秒延時問題’,考證以後發現不對,給你們帶來困擾實在抱歉。
考證以後發現VUE的click事件都是原生的click並無處理這個延時。爲了避免讓你們困擾,github上的demo已經使用fastClick解決了延時問題,(以前太懶了>.<)。如今原生的js實現效果也很順暢了。
考慮到項目裏有的應用場景有多個輸入框,固然輸入的時候只須要一個鍵盤,所以組件化的時候將輸入框做爲一個組件v-input,鍵盤做爲一個組件v-keyboard。
交互圖以下:
圖6 VUE組件交互圖
複製代碼
考慮到本項目裏面存在一個頁面多個輸入框的場景,所以須要控制鍵盤與哪一個輸入框配合使用。
爲了達到這樣的目的,採用「當點擊輸入框獲取焦點的時候,將當前v-input輸入框組件的實例傳給v-keyboard鍵盤組件」的方式。
this.$refs.virtualKeyBoard.$emit('getInputVm', this.$refs.virtualInput);
如圖6 ,v-keyboard組件會監聽'getInputVm'事件,獲取v-input的實例。
鍵盤組件v-keyboard獲取到輸入框組件v-input的實例以後就能夠根據鍵盤的點擊事件——添加或刪除,操做輸入框組件v-input來放入或者刪除字符了。
這樣即便有多個輸入框,也方便控制鍵盤和輸入框之間的操做。
需求裏要求進入某個頁面輸入框自動獲取焦點,鍵盤自動彈出。
<v-input
ref="virtualInput"
v-model="amount"
:placeholder="placeText"
:is-auto-focus="true"
@show-key-board="showKeyBoard">
</v-input>
複製代碼
this.$refs.virtualKeyBoard.$emit('getInputVm', this.$refs.virtualInput);
複製代碼
鍵盤組間捕獲'getInputVm'事件以後獲取了相應輸入框的實例,同時自動彈出。
this.$on('getInputVm', function(obj) {
this.refObject = obj;
this.isShow = true;
});
複製代碼
vue支持自定義v-model,子組件設置一個value 的 props。
props: {
value: {
type: String,
default: '',
},
}
複製代碼
在value改變的時候$emit一個'input'事件並把相應的值傳出去就能夠實現v-model的雙向綁定了。this.getInputStr()是用來獲取輸入框中字符串的函數。
this.$emit('input', this.getInputStr());
複製代碼
效果以下:
原生的input 設置type =number,想要作輸入前驗證控制小數點個數和小數位數等功能基本很難實現,要在輸入前取到值也是存在各類兼容性問題,目前只有ontextInput在移動端能在輸入前準確取到值,還有個關鍵的問題type =number的時候取到的value不包含小數點,致使輸入前使用正則驗證幾乎沒法實現;如果設置type= text 雖然能取到輸入框中全部字符,可是就沒法彈出數字鍵盤。要想使用原生input輸入小數,就必須有所取捨。
很不幸,我就是一個強迫症癌晚期患者,目前實現的鍵盤套件改形成VUE組件已經成功在項目中使用,有單輸入框的頁面,也有多輸入框的頁面,支持placeholder 和v-model。