在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)的更多相关文章

  1. 限制input[type=number]的输入位数策略整理

    当我们使用类型number的input输入框的时候,我们可能需要限制输入的位数,这个时候通常会想到maxlength,但是maxlength是在number类型的时候是不支持的,下面是一些解决这种问题 ...

  2. 动手写个数字输入框1:input[type=number]的遗憾

    前言  最近在用Polymer封装纯数字的输入框,开发过程中发现不少坑,也有很多值得研究的地方.本系列打算分4篇来叙述这段可歌可泣的踩坑经历: <动手写个数字输入框1:input[type=nu ...

  3. input type = number 去除上下箭头,禁用滚轮事件(默认的自带滚轮加减数字)

    <style type="text/css"> /*盒子大小从边框开始计算*/ html * { box-sizing: border-box; } /*解决模态框抖动 ...

  4. 兼容IE7、IE8、IE9的input type="number"插件

    IE11版本好像才兼容input type="number",但是现在Win7版本操作系统下,很多人的IE版本都是IE7/8/9,所以为了体验就自己写了一个小插件,支持设置最大值. ...

  5. 输入类型<input type="number"> / input标签的输入限制

    输入限制 属性 描述 disabled 规定输入字段应该被禁用. max 规定输入字段的最大值. maxlength 规定输入字段的最大字符数. min 规定输入字段的最小值. pattern 规定通 ...

  6. input[type=number]问题

    有时候对于只能输入数字的表单会有想要写成input[type=number]但是其中有一个问题 <input type="text" name="code" ...

  7. input type=number 禁止输入字符“e”的办法

    输入框input,的type设置为number,本想只输入数字,但是字符“e”却能通过, 首先科普一下, <body> <input onkeypress="getCode ...

  8. 解决 html5 input type='number' 类型可以输入e

    当给 input 设置类型为 number 时,比如,我想限制,只能输入 0-9 的正整数,正则表达式如下: /^[-]?$/ // 匹配 0-9 的整数且只匹配 0 次或 1 次 用正则测试,小数点 ...

  9. 去掉 input type="number" 右边图标

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

随机推荐

  1. CSS篇(上)

    紧接着HTML篇的CSS篇开啦,老铁们快来围观... 1.介绍一下标准的CSS盒子模型?低版本IE的盒子模型有什么不同? 1>有两种:IE盒子模型      W3C盒子模型 2>盒模型:内 ...

  2. nova创建虚拟机源码分析系列之五 nova源码分发实现

    前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...

  3. canvas学习api

    1.canvas.getContext():获取渲染上下文和绘画功能: 一.绘制矩形 2.ctx.fillRect(x,y,width,height):绘制矩形: 3.ctx.strokeRect(x ...

  4. Python学习(五):易忘知识点

    1.列表比较函数cmp >>> a = [1,2,3,4] >>> b = [1,2,3,4,5] >>> c = [1,2,3,4] >& ...

  5. 为什么硬链接不能链接目录、文件inode 和目录 dentry 的区别联系

    我们对任何一个目录用ls -l 命令都可以看到其连接数至少是2,这也说明了系统中是存在硬连接的,而且命令ln -d 也可以让超级用户对目录作硬连接,这些都说明了系统限制对目录进行硬连接只是一个硬性规定 ...

  6. JMeter 插件管理

    JMeter管理的插件包括了jmeter-plugins.org上常用的插件以及各种第三方插件和JMeter核心插件. JMeter插件管理器主要管理插件安装,卸载,升级等操作. 安装插件管理 1.下 ...

  7. 浏览器中的user-agent的几种模式

    服务器一般会根据访问的浏览器进行识别,针对不同浏览器才用不同的网站样式及结构,也是通过这个信息判断用户使用的平台模式(手机,pc或平板) 识别为手机一般有这几个关键字: "Windows P ...

  8. 未能解析此远程名称: 'www.***.com' 解决办法

    今天发布网站的时候在本机IIS上调试完全没问题,但发布到远程服务器上却出现了如下错误: 原因:远程服务器的DNS解析失败,不能对类似于'www.***.com' 的网站解析,所以我们要进行手动解析. ...

  9. <转>LOG日志级别

    Level Description Example emerg Emergencies - system is unusable 紧急 - 系统无法使用 Child cannot open lock ...

  10. 漂亮的提示框SweetAlert使用教程

    一.简介 所使用过的弹出框插件,SweetAlert是最好用的.发展至今,已经有两个版本,一个是原版 t4t5/sweetalert , 一个是分支版 limonte/sweetalert2 ,更新相 ...