zoukankan      html  css  js  c++  java
  • 【笔记】移动端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>
    View Code

    第一,首先想到额就是在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);
        },
    View Code

      2)数字键盘input type=number,会导致maxlength失效,无法限制长度

      解决:用slice(0, max)处理

          if (formattedValue.length > this.max) {
            formattedValue = formattedValue.slice(0, this.max);
          }
    View Code

      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);
    View Code

       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;
            }
          };
        }
      }
    View Code

    第三,其他说明

      为什么不用 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>
    View Code
  • 相关阅读:
    Mysql 用户管理
    php插件名称 yum安装
    U盘模式无法引导进入pe系统
    修改 ssh 远程连接 时间
    tomcat 安装在 linux
    tomcat 配置文件 server.xml
    Linux 安装 jdk
    高可用web架构: LVS+keepalived+nginx+apache+php+eaccelerator(+nfs可选 可不选)
    Keepalived 工作原理和配置说明
    Mysql 初始化 及 密码管理
  • 原文地址:https://www.cnblogs.com/smileSmith/p/8178169.html
Copyright © 2011-2022 走看看