zoukankan      html  css  js  c++  java
  • 一个数字键盘引发的血案——移动端H5输入框、光标、数字键盘全假套件实现

    https://juejin.im/post/5a44c5eef265da432d2868f6  

    为啥要写假键盘?

    还是输入框、光标全假的假键盘?

    手机自带的不用非得写个假的,吃饱没事干吧?

    装逼?炫技?

    宝宝也是被逼的,宝宝也很委屈~.~

    问题产生背景

    移动端H5项目需求点:

    进入某页面自动弹出带小数点的数字键盘,并且自带输入验证,比如金额——只能输入数字和小数点,并且只能输入一位小数点、小数位不超过2位,且输入前验证不合法就不让输入、(UE特加功能——定制光标颜色>.<简直是反人类的需求)。细分如下:

    • 进入相关页面,输入框自动获取焦点
    • 键盘自动弹出
    • 弹出带小数点的数字键盘
    • 数字输入前自动验证,只能输入一个小数点,小数位数不超过2位,超过就不能继续输入
    • 如果光标在第一位,此时键入的是'.',则自动放入'0'再插入'.'

    实现方案拟定

    1. 基于input + 手机自带键盘实现方案

    (1)针对功能点1,可以给 input 设置属性 autofocus , 输入框就能自动聚焦。 轻松搞定

    (2)针对功能点2 ,给input设置属性 autofocus 会自动聚焦但是键盘并不会自动弹出;

    必须手动点击输入框键盘才会弹出; 于是在进入页面的时候用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.'

    图1 原生input type=number 效果

    这时候聪明的你一定想到要使用事件监听键入的字符,在输入之前进行判断,然后决定是否放入输入框。

    你肯定又会开心的想到一堆可能有用的事件:onkeydown,onkeyup,onchange,oninput,onpropertychange,textInput。

    路漫漫其修远兮啊~经过不断尝试之后仍然发现很多问题。

    • onkeyup——虽然每增加删除字符都会触发,但增加字符的时候是值输入之后才触发,无法做到输入前验证;
    • onchange——是在内容改变(两次内容有可能相等)且失去焦点时触发,也无法做到输入前验证。
    • onpropertychange——onchange事件在内容改变(两次内容有可能还是相等的)且失去焦点时触发;即每增加或删除一个字符就会触发,通过js改变也会触发该事件,但是该事件IE专有。
    • oninput——移动端很多手机不支持。

    (只剩下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获取到的是' '空字符串,获取不到小数点。这样就无法判断是否输入小数点,因而不能判断是否还能输入小数点,那就还是能输入无数个小数点,问题依然得不到解决。

    尝试:

    • 使用VUE中双向绑定的this.amount来获取输入的所有字符,发现this.amount获取到的和value获取值的情况相同。尝试失败。
    • 通过textInput获取到的输入值,自己维护一个字符数组。但是textInput在删除时不会触发,因而不能实时获取input输入框里面的所有准确字符;而且由于无法获取光标在input输入框的具体位置而无法确定删除的是哪个字符,因而字符数组无法准确维护。尝试失败。

    (5)针对功能点5,功能4解决了,功能5是小case。。。

    所以基于input + 手机自带键盘实现方案要满足以上需求难以实现

    2. 基于input + 假数字键盘实现方案

    若是用假键盘加原生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开发项目,考虑兼容性,显然以上方法不能兼容大部分的机型。

    3. 输入框、光标、数字键盘全假实现方案

    以上两种方案均难以实现,因此我只能大胆想象,要实现满足以上需求的假键盘就得实现假输入框、假光标、假keyboard的一套装备。这样所有的元素我都能控制,上面的那些问题全部可以解决。

    雏形若是实现只能从最后面增加删除没有光标的假键盘非常容易,只需要给每个键绑定一个click事件,维护一个数组,每次从后面push或者pop就能维护输入框中的内容。

     图3 只能从最后添加、删除且没有光标的效果图
    复制代码

    但是这样跟正真的输入框效果比体验太差了。

    难点

    要实现体验跟原生键盘一样并且自带输入验证的假键盘,难点主要在于:

    • 有光标,且光标闪动
    • 光标定位,点击数字中间光标自动移过去
    • 根据光标的位置实现插入删除
    • 失去焦点光标隐藏,点击输入框光标显示并且弹出键盘

    原生js实现

    对于光标实现,创造一个元素设置背景色,可以控制它隐藏和出现。

    对于“点击数字中间光标自动移过去 ”,可以每添加一个数字或者小数点就先加一个带点击事件的空元素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实现效果也很顺畅了。

    VUE组件化

    考虑到项目里有的应用场景有多个输入框,当然输入的时候只需要一个键盘,因此组件化的时候将输入框作为一个组件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来放入或者删除字符了。

    这样即使有多个输入框,也方便控制键盘和输入框之间的操作。

    输入框自动获取焦点,键盘自动弹出

    需求里要求进入某个页面输入框自动获取焦点,键盘自动弹出。

    • 输入框自动获取焦点可以通过设置is-auto-focus来控制是否自动获取焦点。
    <v-input
        ref="virtualInput"
        v-model="amount"
        :placeholder="placeText"
        :is-auto-focus="true"
        @show-key-board="showKeyBoard">
    </v-input>
    复制代码
    • 要自动弹出键盘如图6,需要在页面实例化完成之后将相应的输入框组件v-input的实例传给键盘组件v-keyboard。
    this.$refs.virtualKeyBoard.$emit('getInputVm', this.$refs.virtualInput);
    复制代码

    键盘组间捕获'getInputVm'事件之后获取了相应输入框的实例,同时自动弹出。

    this.$on('getInputVm', function(obj) {
         this.refObject = obj;
         this.isShow = true;
    });
    复制代码

    v-model支持

    vue支持自定义v-model,子组件设置一个value 的 props。

    props: {
        value: {
          type: String,
          default: '',
        },
    }
    复制代码

    在value改变的时候$emit一个'input'事件并把相应的值传出去就可以实现v-model的双向绑定了。this.getInputStr()是用来获取输入框中字符串的函数。

    this.$emit('input', this.getInputStr());
    复制代码

    效果如下:

    源码参见github.com/DaisyWang88…

    总结

    原生的input 设置type =number,想要做输入前验证控制小数点个数和小数位数等功能基本很难实现,要在输入前取到值也是存在各种兼容性问题,目前只有ontextInput在移动端能在输入前准确取到值,还有个关键的问题type =number的时候取到的value不包含小数点,导致输入前使用正则验证几乎无法实现;若是设置type= text 虽然能取到输入框中所有字符,但是就无法弹出数字键盘。要想使用原生input输入小数,就必须有所取舍。

    • 要么不做输入前验证,使用type = number ,可以输入多个小数点,只在数值数值不合法的时候提示输入不合法,但是只有android可以弹出数字键盘,IOS仍然弹出全键盘。用户体验可能差些。
    • 要么使用type = text,虽然可以做到输入前验证(因为可以取到全部字符),但是所有机型都只会弹全键盘了,用户体验也一般。
    • 以上两种都无法实现进入页面键盘自动弹出,只能借助NA提供的方法实现。
    • 如果你是强迫症癌晚期患者,用户体验之上者,那么你就可以跟我一样做个假键盘,这样以上问题都不是问题。还可以添加附加功能,比如输入的时候若在第一位输入小数点的时候,前面自动补'0';删除的时候,若小数点在第一位前面自动补'0';还可以定制光标颜色、键盘样式等等。

    很不幸,我就是一个强迫症癌晚期患者,目前实现的键盘套件改造成VUE组件已经成功在项目中使用,有单输入框的页面,也有多输入框的页面,支持placeholder 和v-model。


    作者:百度外卖大前端技术团队
    链接:https://juejin.im/post/5a44c5eef265da432d2868f6
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    分布式-通信(NIO&BIO&网络模型&零拷贝)
    cmake构建和链接静态库与动态库
    ldd
    CMAKE_BUILD_TYPE
    C/CXX attribute
    ffmpeg使用说明(2):ffmpeg提取video,audio,yuv/rgb,PCM
    ffmpeg使用说明(1):ffmpeg帮助使用指南
    ffmpeg使用说明(0):ffmpeg/ffplay/ffprobe
    ffplay使用帮助说明
    书写log的艺术
  • 原文地址:https://www.cnblogs.com/chaoyuehedy/p/9530380.html
Copyright © 2011-2022 走看看