为啥要写假键盘?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。