密码输入框组件的实现

变更记录:

  • 增加中文输入限制
  • 增加粘贴限制

一般情况下,如果要实现密码输入框,采用浏览器原生的密码输入框是很好的选择。比如登录界面,使用浏览器原生的密码输入框,用户就可以使用浏览器自身的‘’记住密码“功能,不用每次登录都需要手动输入账号密码。但是呢!有某些情况下,只需要密码框的密码显示/隐藏功能。如果使用浏览器原生的密码框,“记住密码”功能的弹窗,就会造成很大的不便。所以,在这种情况下,就需要模拟实现密码框的功能了。

密码框的功能解析

在实现密码框之前,首先要了解密码框的功能。

密码框的功能有:

  • 文本输入功能
  • 密码显示隐藏功能

文本输入功能

首先,密码框能像普通的文本输入框一样,进行密码的输入修改。此外,用户输入的密码不能明码显示出来,要转化成特殊字符()显示。

要实现这一点,需要做两个步骤:

  1. 使用普通的文本输入框来进行密码输入
  2. 给文本输入框添加 input 事件,用户每输入一个字符,都要将用户输入的字符存储起来,并将其转化为特殊字符()在输入框中显示出来。(存储起来的字符才是真正的密码)

密码显示隐藏功能

密码框还需要一个密码显示隐藏的按钮。用户点击这个按钮,可以控制密码框中的密码是明码显示的还是隐藏的。

限制中文的输入

原生的密码输入框是限制中文输入法的。所以,我们也要对中文做限制。
这里通过两种途径来实现中文输入的限制:

  • 限制中文输入法,只允许英文输入法。
  • 将用户输入的中文字符替换为空
限制中文输入法

ime-mode:

  • 用途:CSS属性,用于设置或检索是否允许用户激活输入中文,韩文,日文等的输入法(IME)状态。
  • 可选值:
  • auto不改变输入法状态,此为预设值。
  • normal输入法设为一般状态,使用者可在自订样式表中盖过网页的设定。Internet Explorer不支援此值。active输入法设为启用状态。除非使用者刻意关闭、否则此文字栏位将使用输入法工具。Linux不支援此值。
  • inactive输入法设为关闭状态,但使用者仍可另行启用。Linux不支援此值。
  • disabled输入法设为停用状态,在此栏位中使用者亦无法将其启用。
  • 用法:
<input type="text" name="name" value="initial value" style="ime-mode: disabled">

显然,借助ime-mode,可以轻松实现中文输入的限制。

然而,在 Chrome 浏览器中,ime-mode 属性是失效的。因为:

“ime-mode”是在某些浏览器中实现的一个属性,这是有问题的,并且被这个规范淘汰了。

浏览器兼容性
FeatureChromeEdgeFirefox (Gecko)Internet ExplorerOperaSafari (WebKit)
Basic supportNo support(Yes)3.0 (1.9)5.0 1No supportNo support
FeatureAndroidEdgeFirefox Mobile (Gecko)IE PhoneOpera MobileSafari Mobile
Basic support?(Yes)????

所以,我们需要一个另外一种限制中文输入的方法。我的解决方案是:将用户输入的中文字符替换为空

将用户输入的中文字符替换为空

首先,我们先看一个动图:

在这里插入图片描述

可以看出,在进行中文输入的过程中,所按的字符会同步输入到输入框中。这样就很难区分开哪些字符是进行中文输入是产生的字符了。区分不出来,就不能有效的限制中文输入,极有可能照成“误杀”。

怎么解决呢?

HTML 提供了几个这样的事件:

compositionstart:(中文输入开始)

文本合成系统如 input method editor(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。

例如,当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。

compositionupdate:(中文输入中)

compositionupdate 事件触发于字符被输入到一段文字的时候(这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)

compositionend:(中文输入结束)

当文本段落的组成完成或取消时, compositionend 事件将被触发 (具有特殊字符的触发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议)。

很幸运,借助这三个事件,我们可以清楚的知道用户什么时候进行中文输入,什么时候结束中文输入。

因此,我们的解决思路可以这样:

  • 用户不进行中文输入时,用户每输入一个字符,都进行处理;
  • 用户开始进行中文输入后,暂停处理操作;
  • 等用户完成中文输入后,立即替换用户输入的中文字符。

这样我们就可以实现:将用户输入的中文字符替换为空

密码框的实现

原生 JavaScript

// 密码输入框组件
class PasswordInput {
  /**
   * 构造函数
   * @param {String} constainer_selector 密码输入框父(容器)元素的样式选择器
   * @param {Function} toggleCallback  密码显示隐藏回调函数
   */
  constructor(constainer_selector, toggleCallback) {
    this.compositionStatue = false; // 中文输入的标记符:true 为正在进行中文输入
    this.compositionStartCursorIndex = 0; // 记录进行中文输入时光标的位置
    this._containerElem = document.querySelector(constainer_selector); // 密码框父(容器)元素
    this._inputElem = document.querySelector(
      `${constainer_selector} .password-input`
    ); // 密码框 input 本身
    this._btnToggleElem = document.querySelector(
      `${constainer_selector} .btn-toggle`
    ); // 密码显示隐藏按钮元素

    /**
     * 密码显示隐藏回调函数
     * @param {Element} 按钮本身
     * @param {String} 显示隐藏标识:show / hide
     */
    this._toggleCallback = toggleCallback;

    if (this._inputElem) {
      this.initInput();
    }

    if (this._btnToggleElem) {
      // 添加显示隐藏按钮点击事件
      this._btnToggleElem.addEventListener('click', this.btnToggleElemClick);
      this._btnToggleElem.addEventListener('paste', function() {
        return false;
      });
    }
  }

  /**
   * input 初始化
   */
  initInput() {
    this._inputElem.status = 'hide'; // 初始时,默认隐藏状态
    this._inputElem.pwdValue = ''; // pwdValue 属性,用于存储真正的密码
    this._inputElem.type = 'text'; // 强制 input type 为 text, 防止用户设置 password 类型从而造成影响
    this._inputElem.setAttribute('ime-mode', 'disabled');
    if (this._inputElem.value) {
      // 如果存在默认密码,则将默认密码存入 pwdValue,并渲染一次密码
      this._inputElem.pwdValue = this._inputElem.value;
      this.toggleRender();
    }

    // 开始进行中文输入时触发的事件
    this._inputElem.addEventListener('compositionstart', () => {
      this.compositionStartCursorIndex = this._inputElem.selectionStart; // 记录进行中文输入时光标的位置
      this.compositionStatue = true;
    });

    // 中文输入结束后触发的事件
    this._inputElem.addEventListener('compositionend', () => {
      this.limitCN();
      this.compositionStatue = false;
      this.compositionLength = 0;
    });

    // input 事件
    this._inputElem.addEventListener('input', () => {
      if (this.compositionStatue) {
        // 进行中文输入时不执行 inputHandle 函数
        return false;
      }
      this.inputHandle();
    });

    // 禁止粘贴
    this._inputElem.addEventListener('paste', (e) => {
      e.preventDefault();
      return false;
    });
  }

  // input 事件
  inputHandle = () => {
    const val = this._inputElem.value;
    let newPwd = ''; // 存储新的真正密码
    let oldPwd = this._inputElem.pwdValue || ''; // 获取存储的真正密码,将其定为旧密码
    const cursorIndex = this._inputElem.selectionStart; // 获取光标在输入框中的位置

    if (this._inputElem.status == 'hide') {
      // 当密码需要隐藏时,将密码转为*,真正的密码为 pwdValue
      if (oldPwd && oldPwd.length > val.length) {
        // 旧的真实密码存在,且其字符串长度大于输入框的字符串长度,说明用户进行删除操作
        const compositionStatue = oldPwd.length - val.length + cursorIndex; // 用户删除的字符串长度加光标的当前的位置,计算得出删除字符串的最后一个字符的位置
        const del_string = oldPwd.substring(cursorIndex, compositionStatue); // 获取用户删除的字符串
        newPwd = oldPwd.replace(del_string, ''); // 将旧的真实密码中对应的删除字符串替换为'',实现对真实密码的删除操作
      } else {
        const reg = /[^•]/.exec(val); // 获取虚假密码中新增的密码字符
        if (reg) {
          // 如果存在新增密码字符,则进行输入输入处理
          newPwd = this.insertStr(oldPwd, reg.index, reg[0]); // 将用户新输入的字符插入旧的真实密码
          this.cursorMove(this._inputElem, reg.index + 1); // 设置光标的位置
        } else {
          // 如果不存在新增密码字符,不做改变
          newPwd = oldPwd;
        }
      }
    } else {
      // 当不需要隐藏密码时,仍需要将密码存入 pwdValue
      newPwd = val;
    }
    this._inputElem.pwdValue = newPwd;
    this.toggleRender();
  };

  // 密码隐藏显示切换按钮 click 事件
  btnToggleElemClick = () => {
    if (this._inputElem.status == 'hide') {
      this._inputElem.status = 'show';
    } else {
      this._inputElem.status = 'hide';
    }
    this._toggleCallback(this._btnToggleElem, this._inputElem.status); /// 执行显示隐藏的回调函数
    this.toggleRender();
  };

  // 密码显示/隐藏切换时,对input value 的处理的渲染函数
  toggleRender() {
    const val = this._inputElem.value;
    if (this._inputElem.status == 'hide') {
      const replaceVal = val.replace(/[^•]/g, '•');
      this._inputElem.value = replaceVal;
    } else {
      this._inputElem.value = this._inputElem.pwdValue;
    }
  }

  /**
   * 根据位置在字符串中插入字符串
   * @params soure 原字符串
   * @params start 位置
   * @params newStr 要插入的字符串
   */
  insertStr(soure, start, newStr) {
    return soure.slice(0, start) + newStr + soure.slice(start);
  }

  /**
   * 控制光标的位置
   */
  cursorMove(elem, spos) {
    // spos 光标的位置 -1为最后一位
    if (spos < 0) spos = elem.value.length;
    if (elem.setSelectionRange) {
      //兼容火狐,谷歌
      setTimeout(function() {
        elem.setSelectionRange(spos, spos);
        elem.focus();
      }, 0);
    } else if (elem.createTextRange) {
      //兼容IE
      var rng = elem.createTextRange();
      rng.move('character', spos);
      rng.select();
    }
  }

  // 限制中文输入
  limitCN() {
    let val = this._inputElem.value; // 获取输入框中的值、
    val = val.replace(/[^\x00-\x80•]/gi, ''); // 把所有双字节字符替换为空(排除•)
    this._inputElem.value = val;
    this.cursorMove(this._inputElem, this.compositionStartCursorIndex); // 将光标重置为中文输入前的位置
  }
}

使用方法:

<body>
  <div class="pwd">
    <input type="password" class="password-input" value="1111" />
    <button class="btn-toggle" type="button">切换</button>
  </div>
  <script src="PasswordInput.js"></script>
  <script>
    window.onload = function() {
      new PasswordInput('.pwd', (e, status) => {
        console.log(e);
        console.log(status);
      });
    };
  </script>
</body>

注意,input 一定要添加class="password-input", 切换按钮一定要添加class="btn-toggle"

Vue 2.x

组件:

<template>
  <div class="password-input" :style="{ width: width }">
    <input
      style="ime-mode: disabled"
      :value="hideValue"
      @compositionstart="compositionstartHandle"
      @compositionend="compositionendHandle"
      @input="inputHandel"
      @paste.capture.prevent="pasteHandle"
      ref="password-input"
      :placeholder="placeholder"
    />
    <div class="btn-show" @click="isShow = !isShow">
      <img
        v-if="isShow"
        src="@/modules/case-show/modules/input-password/assets/pwd-show.png"
      />
      <img
        v-else
        src="@/modules/case-show/modules/input-password/assets/pwd-hide.png"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: 'password-input',
  props: {
    modelVal: String,
    placeholder: {
      type: String,
      default: '请输入'
    },
    width: {
      type: String,
      default: '300px'
    }
  },
  model: {
    prop: 'modelVal', //指向props的参数名
    event: 'input' //事件名称
  },
  data() {
    return {
      isShow: false,
      hideValue: '',
      compositionStatue: false,
      compositionStartCursorIndex: 0
    };
  },
  watch: {
    isShow: function() {
      this.render(this.modelVal);
    },
    modelVal: function(val) {
      this.render(val);
    }
  },
  methods: {
    render(val) {
      if (this.isShow) {
        this.hideValue = val;
      } else {
        this.hideValue = val.replace(/[^•]/g, '•');
      }
    },

    compositionstartHandle() {
      this.compositionStartCursorIndex = this.$refs[
        'password-input'
      ].selectionStart; // 记录进行中文输入时光标的位置
      this.compositionStatue = true;
    },

    compositionendHandle() {
      this.limitCN();
      this.compositionStatue = false;
      this.compositionLength = 0;
    },

    inputHandel() {
      if (this.compositionStatue) {
        // 进行中文输入时不执行 inputHandle 函数
        return false;
      }
      this.formatPassword();
    },

    pasteHandle() {
      return false;
    },

    formatPassword() {
      let new_pwd = ''; // 存储新的真实密码
      let old_pwd = this.modelVal || ''; // 获取旧的真实密码
      const pwd_input_elem = this.$refs['password-input']; // 获取密码输入框DOM
      let val = this.$refs['password-input'].value; // 获取输入框中的值
      const cursorIndex = pwd_input_elem.selectionStart; // 获取光标在输入框中的位置
      if (this.isShow) {
        // 明码显示,不做处理
        new_pwd = val;
      } else {
        // 隐藏密码
        if (old_pwd && old_pwd.length > val.length) {
          // 旧的真实密码存在,且其字符串长度大于输入框的字符串长度,说明用户进行删除操作
          const stop = old_pwd.length - val.length + cursorIndex; // 用户删除的字符串长度加光标的当前的位置,计算得出删除字符串的最后一个字符的位置
          const del_string = old_pwd.substring(cursorIndex, stop); // 获取用户删除的字符串
          new_pwd = old_pwd.replace(del_string, ''); // 将旧的真实密码中对应的删除字符串替换为'',实现对真实密码的删除操作
        } else {
          const reg = /[^•]/.exec(val); // 获取虚假密码中新增的密码字符
          new_pwd = this.insertStr(old_pwd, reg.index, reg[0]); // 将用户新输入的字符插入旧的真实密码
          this.cursorMove(pwd_input_elem, reg.index + 1); // 设置光标的位置
        }
      }
      this.$emit('input', new_pwd);
    },

    /**
     * 根据位置在字符串中插入字符串
     * @params soure 原字符串
     * @params start 位置
     * @params newStr 要插入的字符串
     */
    insertStr(soure, start, newStr) {
      return soure.slice(0, start) + newStr + soure.slice(start);
    },

    /**
     * 控制光标的位置
     */
    cursorMove(elem, spos) {
      // spos 光标的位置 -1为最后一位
      if (spos < 0) spos = elem.value.length;
      if (elem.setSelectionRange) {
        //兼容火狐,谷歌
        setTimeout(function() {
          elem.setSelectionRange(spos, spos);
          elem.focus();
        }, 0);
      } else if (elem.createTextRange) {
        //兼容IE
        var rng = elem.createTextRange();
        rng.move('character', spos);
        rng.select();
      }
    },

    // 限制中文输入
    limitCN() {
      let val = this.$refs['password-input'].value; // 获取输入框中的值
      // eslint-disable-next-line no-control-regex
      val = val.replace(/[^\x00-\x80•]/gi, '');
      this.$refs['password-input'].value = val;
      this.cursorMove(
        this.$refs['password-input'],
        this.compositionStartCursorIndex
      ); // 将光标重置为中文输入前的位置
    }
  }
};
</script>

<style lang="less" scoped>
.password-input {
  position: relative;
  margin: 0 auto;
  input {
    height: 28px;
    width: 100%;
    line-height: 28px;
    padding-left: 10px;
    padding-right: 30px;
    box-sizing: border-box;
  }
  .btn-show {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    display: flex;
    height: 100%;
    width: 30px;
    align-items: center;
    justify-content: flex-start;
    cursor: pointer;
    img {
      width: 20px;
      height: 20px;
    }
  }
}
</style>

使用方法:

<InputPwd v-model="val"></InputPwd>

参考文章

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值