【笔记】移动端H5数字键盘input type=number的处理(IOS和Android)
在Vue中的项目,基于VUX-UI开发,一个常见的需求:
1、金额输入框 2、弹出数字键盘 3、仅支持输入两位小数,限制最大11位数,不允许0开头
后续:与UI沟通后, 思路调整为限制输入,并减少正则替换输入值出现的闪动。后续改动如下,注意点如下:
1、处理思路
A。在用户输入的键盘事件中,对于不符合的输入,阻止默认行为和事件冒泡。
不符合输入的规则如下:
1)当前输入框中的长度大于等于配置的max
2)非数字和小数点
3)当前输入框中已存在小数点,或第一位输入小数点
B。在获取值后,对于不符合两位小数的值,用watch正则替换后,再下一次渲染(会出现先12.000到12.00的闪动)
2、阻止键盘事件在哪个阶段?
keypress。
因为keydown和keyup得到的是keyEvent中键值是原始的组合键值,需要判断不同环境和浏览器对keycode的实现不同以及是否有shift/alt等。比如在IOS中keydown,对于字符$ @,keycode都是0;中文键盘和英文键盘中的数字keycode不一致。
而kepress得到的是组合解析后的实际值,android和ios大部分表现一致。
3、Android的数字键盘中的小数点的特殊处理
调试发现,安卓的数字键盘中,小数点做了特殊处理:
1)无法捕获到keypress事件
2)keydown事件中keEvent的keycode是0,无法用于判断
3)keydown事件中keEvent的keyIdentifier === 'U+0000'
4)在keydown事件以及keyuup或其它事件中, 用preventDefault和stopPropagation阻止默认行为和事件冒泡,不能阻止input框输入小数点.
所以对这个问题处理,只能沿用之前用在watch中处理空值问题的思路。
4、最终效果
IOS中默认拉起含特殊字符的数字键盘,对于非法输入不会出现任何闪动,对于长度越界的会出现闪动
Andriod中默认拉起九宫格数字键盘,没有特殊字符,小数点会出现闪动,对于长度越界的会出现闪动
<template>
<XInput
:title="title"
:max="currentMax"
:min="currentMin"
:type="type"
v-model="currentValue"
@on-focus="onFoucus()"
@on-blur="onBlur()"
:show-clear="showClear"
:placeholder="placeholder"
ref="xinput">
<template v-if="$slots.label" slot="label"><slot name="label"></slot></template>
<template v-if="$slots.right" slot="right"><slot name="right"></slot></template>
</XInput>
</template>
<script>
export default {
data() {
return {
currentValue: this.value,
};
},
computed: {
currentMax() {
return (this.type === 'number') ? undefined : this.max;
},
currentMin() {
return (this.type === 'number') ? undefined : this.min;
}
},
props: {
title: String,
max: Number,
min: Number,
type: String,
showClear: {
type: Boolean,
default: true,
},
placeholder: String,
value: [String, Number],
filter: {
type: Function,
default: (value) => {
let formattedValue = '';
const match = value.match(/^([1-9]\d*(\.[\d]{0,2})?|0(\.[\d]{0,2})?)[\d.]*/);
if (match) {
formattedValue = match[1];
}
return formattedValue;
},
}
},
watch: {
currentValue(val, oldVal) {
// 调用filter过滤数据
let formattedValue = this.filter(val);
if (this.type === 'number') {
formattedValue = this.typeNumberFilter(formattedValue, oldVal);
}
if (val !== formattedValue || val === '') {
setTimeout(() => {
this.currentValue = formattedValue;
}, 0);
}
this.$emit('input', formattedValue);
},
value(value) {
this.currentValue = value;
},
},
methods: {
blur() {
this.$refs.xinput.blur();
},
focus() {
this.$refs.xinput.focus();
},
onFoucus() {
this.$emit('on-focus');
},
onBlur() {
this.$emit('on-blur');
},
typeNumberFilter(val, oldVal) {
const inputEle = this.$refs.xinput.$refs.input;
let formattedValue = val;
// TODO: 待大范围验证:Android处理连续输入..后,type=number的input框会把值修改为'',这里手动替换为上次的currentValue
// 问题描述: 1.00. 不会触发值改变,1.00.不会触发值改变,1.00.【\d\.】都会把值修改为空字符串''。hack处理的条件说明如下:
// 1、输入框拿到的是空值(因input=number导致输入框立即被赋予空值。点击清除按钮时,这里input输入框还是上次的值)
// 2、上次输入值有效
if (inputEle.value === '' && oldVal && oldVal.match(/^(\d)[\d.]+/)) {
formattedValue = oldVal;
}
return formattedValue;
},
isBackspace(keyCode) {
return keyCode === 8;
},
isDot(keyCode) {
return keyCode === 46 || keyCode === 190;
},
isNumber(keyCode) {
return (keyCode >= 48 && keyCode <= 57);
},
isNotNumberKeycode(keyCode) {
return !this.isBackspace(keyCode) && !this.isDot(keyCode) && !this.isNumber(keyCode);
},
isDotStart(keyCode, inputVal) {
return this.isDot(keyCode) && (!inputVal || inputVal === '' || /\./.test(inputVal));
},
isFinalInput(inputVal) {
return inputVal.length >= this.max;
}
},
mounted() {
if (this.type === 'number') {
const inputEle = this.$refs.xinput.$refs.input;
inputEle.onkeydown = (e) => {
// Android小数点特殊处理
const inputVal = inputEle.value;
if (e.keyIdentifier === 'U+0000' && (!inputVal || inputVal === '')) {
inputEle.value = '';
}
};
// eslint-disable-next-line
inputEle.onkeypress = (e) => {
const keyCode = e.keyCode;
const inputVal = inputEle.value;
if (this.isNotNumberKeycode(keyCode) || this.isDotStart(keyCode, inputVal)
|| this.isFinalInput(inputVal)) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
}
}
};
</script>
第一,首先想到额就是在VUX-UI中制定type=number。--不可行
VUX中的文档和代码说明,type=number不支持maxLength,会报错,而且没有正则替换的处理或者钩子函数,只有输入后提示校验信息。
第二,基于VUX中XInput封装,有如下问题
1)两层v-model,正则替换的值不会触发input框渲染
解决:currentValue赋值为foramttedValue,放入setTimeout(func ,0)中,让input框先渲染为正则替换前的值,再渲染为替换后的值
currentValue(val, oldVal) {
// 调用filter过滤数据
let formattedValue = this.filter(val);
if (this.type === 'number') {
formattedValue = this.typeNumberFilter(formattedValue, oldVal);
}
if (val !== formattedValue || val === '') {
setTimeout(() => {
this.currentValue = formattedValue;
}, 0);
}
this.$emit('input', formattedValue);
},
2)数字键盘input type=number,会导致maxlength失效,无法限制长度
解决:用slice(0, max)处理
if (formattedValue.length > this.max) {
formattedValue = formattedValue.slice(0, this.max);
}
3)数字键盘input type=number ,连续输入小数点...导致实际值和显示值不一致
解决:用原生的 inputElement.value = oldValue处理
const inputEle = this.$children[0].$refs.input;
// TODO: 待大范围验证:处理连续输入..后,type=number的input框会把值修改为''的问题;fastclick导致type=number报错
// 问题描述: 1.00. 不会触发值改变,1.00.不会触发值改变,1.00.【\d\.】都会把值修改为空字符串''。hack处理的条件说明如下:
// 1、当校验后是空值,(因input=number,formattedValue为''表明 原始newVal也为'')
// 2、输入框拿到的是空值(因input=number导致输入框立即被赋予空值。点击清除按钮时,这里input输入框还是上次的值)
// 3、上次输入大于两位(避免最后一位无法删除的问题。最后一位删除时,oldVal.length === 1)
if (formattedValue === '' && inputEle.value === '' && oldVal && oldVal.match(/^(\d)[\d.]+/)) {
formattedValue = oldVal;
}
setTimeout(() => {
inputEle.value = formattedValue;
}, 0);
4)IOS中数字键盘有%$*等特殊字符
解决:用原生的 inputElement.onkeydown监听事件,非数字和退格和小数点直接return事件
mounted() {
if (this.type === 'number') {
const inputEle = this.$refs.xinput.$refs.input;
// eslint-disable-next-line
inputEle.onkeydown = (e) => {
const keyCode = e.keyCode;
if (!this.isBackspace(keyCode) && !this.isDot(keyCode) && !this.isNumber(keyCode)) {
// 其他按键
e.preventDefault();
e.stopPropagation();
return false;
}
};
}
}
第三,其他说明
为什么不用 type=tel?
type=tel在ios中没有小数点
第四,全部代码
<template>
<XInput
:title="title"
:max="currentMax"
:min="currentMin"
:type="type"
v-model="currentValue"
@on-focus="onFoucus()"
@on-blur="onBlur()"
:show-clear="showClear"
:placeholder="placeholder"
ref="xinput">
<template v-if="$slots.label" slot="label"><slot name="label"></slot></template>
<template v-if="$slots.right" slot="right"><slot name="right"></slot></template>
</XInput>
</template>
<script>
export default {
data() {
return {
currentValue: this.value,
};
},
computed: {
currentMax() {
return (this.type === 'number') ? undefined : this.max;
},
currentMin() {
return (this.type === 'number') ? undefined : this.min;
}
},
props: {
title: String,
max: Number,
min: Number,
type: String,
showClear: {
type: Boolean,
default: true,
},
placeholder: String,
value: [String, Number],
filter: {
type: Function,
default: (value) => {
let formattedValue = '';
const match = value.match(/^([1-9]\d*(\.[\d]{0,2})?|0(\.[\d]{0,2})?)[\d.]*/);
if (match) {
formattedValue = match[1];
}
return formattedValue;
},
}
},
watch: {
currentValue(val, oldVal) {
// 调用filter过滤数据
let formattedValue = this.filter(val);
if (this.type === 'number') {
formattedValue = this.typeNumberFilter(formattedValue, oldVal);
}
if (val !== formattedValue || val === '') {
setTimeout(() => {
this.currentValue = formattedValue;
}, 0);
}
this.$emit('input', formattedValue);
},
value(value) {
this.currentValue = value;
},
},
methods: {
onFoucus() {
this.$emit('on-focus');
},
onBlur() {
this.$emit('on-blur');
},
typeNumberFilter(val, oldVal) {
const inputEle = this.$refs.xinput.$refs.input;
let formattedValue = val;
// 由于type=number不支持maxLength,用slice模拟
if (formattedValue.length > this.max) {
formattedValue = formattedValue.slice(0, this.max);
}
// TODO: 待大范围验证:处理连续输入..后,type=number的input框会把值修改为''的问题;fastclick导致type=number报错
// 问题描述: 1.00. 不会触发值改变,1.00.不会触发值改变,1.00.【\d\.】都会把值修改为空字符串''。hack处理的条件说明如下:
// 1、当校验后是空值,(因input=number,formattedValue为''表明 原始newVal也为'')
// 2、输入框拿到的是空值(因input=number导致输入框立即被赋予空值。点击清除按钮时,这里input输入框还是上次的值)
// 3、上次输入大于两位(避免最后一位无法删除的问题。最后一位删除时,oldVal.length === 1)
if (formattedValue === '' && inputEle.value === '' && oldVal && oldVal.match(/^(\d)[\d.]+/)) {
formattedValue = oldVal;
}
setTimeout(() => {
inputEle.value = formattedValue;
}, 0);
return formattedValue;
},
isBackspace(keyCode) {
return keyCode === 8;
},
isDot(keyCode) {
return keyCode === 46 || keyCode === 110 || keyCode === 190;
},
isNumber(keyCode) {
return (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105);
},
},
mounted() {
if (this.type === 'number') {
const inputEle = this.$refs.xinput.$refs.input;
// eslint-disable-next-line
inputEle.onkeydown = (e) => {
const keyCode = e.keyCode;
if (!this.isBackspace(keyCode) && !this.isDot(keyCode) && !this.isNumber(keyCode)) {
// 其他按键
e.preventDefault();
e.stopPropagation();
return false;
}
};
}
}
};
</script>
【笔记】移动端H5数字键盘input type=number的处理(IOS和Android)的更多相关文章
- 限制input[type=number]的输入位数策略整理
当我们使用类型number的input输入框的时候,我们可能需要限制输入的位数,这个时候通常会想到maxlength,但是maxlength是在number类型的时候是不支持的,下面是一些解决这种问题 ...
- 动手写个数字输入框1:input[type=number]的遗憾
前言 最近在用Polymer封装纯数字的输入框,开发过程中发现不少坑,也有很多值得研究的地方.本系列打算分4篇来叙述这段可歌可泣的踩坑经历: <动手写个数字输入框1:input[type=nu ...
- input type = number 去除上下箭头,禁用滚轮事件(默认的自带滚轮加减数字)
<style type="text/css"> /*盒子大小从边框开始计算*/ html * { box-sizing: border-box; } /*解决模态框抖动 ...
- 兼容IE7、IE8、IE9的input type="number"插件
IE11版本好像才兼容input type="number",但是现在Win7版本操作系统下,很多人的IE版本都是IE7/8/9,所以为了体验就自己写了一个小插件,支持设置最大值. ...
- 输入类型<input type="number"> / input标签的输入限制
输入限制 属性 描述 disabled 规定输入字段应该被禁用. max 规定输入字段的最大值. maxlength 规定输入字段的最大字符数. min 规定输入字段的最小值. pattern 规定通 ...
- input[type=number]问题
有时候对于只能输入数字的表单会有想要写成input[type=number]但是其中有一个问题 <input type="text" name="code" ...
- input type=number 禁止输入字符“e”的办法
输入框input,的type设置为number,本想只输入数字,但是字符“e”却能通过, 首先科普一下, <body> <input onkeypress="getCode ...
- 解决 html5 input type='number' 类型可以输入e
当给 input 设置类型为 number 时,比如,我想限制,只能输入 0-9 的正整数,正则表达式如下: /^[-]?$/ // 匹配 0-9 的整数且只匹配 0 次或 1 次 用正则测试,小数点 ...
- 去掉 input type="number" 右边图标
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
随机推荐
- S7-200和S7-300profibus-DP通信
一.S7-200CN的cup可以通过EM277接入DP网络 二.CPU315-2DP做主站,S7-200CUP做从站 三. 通信题目 四.硬件组态 1.主站的DP组态,地址为2 2.EM277作为从站 ...
- Java 本地开发环境搭建(框架采用 Spring+Spring MVC+Hibernate+Jsp+Gradle+tomcat+mysql5.6)
项目搭建采用技术栈为:Spring+Spring MVC+Hibernate+Jsp+Gradle+tomcat+mysql5.6 搭建环境文档目录结构说明: 使用Intellj Idea 搭建项目过 ...
- 绝世emacs配置for Ubuntu
反正过不了几天就要退役了,把emacs配置放出来造福(祸害)大众? 浓浓的OIER风格,除了方便打代码就没别的用处(F8并不这样认为?),只可惜windows下的弄丢了,只有Ubuntu下的. F1不 ...
- chattr 命令详解
chattr 作用: 改变文件属性,这项指令可改变存放在ext2文件系统上的文件或目录属性,这些属性共有一下8种模式 模式: a: 让文件或目录仅供附加用途 b: 不更新文件或目录的最后存取时间 ...
- Qt编写导航按钮
做各种各样的界面的时候,经常需要做一排按钮用于切换到对应界面,俗称导航按钮或者导航菜单,参照过各种各样的主界面导航布局,特意编写导航按钮自定义控件,结合各种情况,继承自QPushButton.已集成在 ...
- Python文件读写 - 读一个文件所有行,加工后写另一个文件
#Filename: file_read_and_write.py #打开文件,cNames读取所有行,储存在列表中,循环对每一行在起始处加上序号1,2,3,4 with open(r'file/co ...
- Angular5系列教程:ng-book2-angular-5-r66 土家翻译,话糙理不糙
嗯, 在工作还辣么忙之时,看了这本书,感觉很不错.想分享给国内朋友们.结合自己的理解和整理加翻译,可能有点糙,但是,话糙理不糙嘛.出系列,不知道会不会弃坑,不立Flag了.持续更新.....我会放在印 ...
- js 类数组arguments详解
arguments并不是一个真正的数组,而是一个"类似数组(array-like)"的对象: 就像下面的这段输出,就是典型的类数组对象: [, , callee: ƒ, Symbo ...
- HTTPS、证书与使用Charles抓包
.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font-size: 14.0px; font-family: "Helvetica Neue", & ...
- 认证客户端的链接与socketserver实现并发
from socket import * import hmac,os secret_key=b'linhaifeng bang bang bang' def conn_auth(conn): ''' ...