密码输入框组件的实现

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

密码框的功能解析

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

密码框的功能有:

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

文本输入功能

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

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

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

密码显示隐藏功能

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

密码框的实现

原生 JavaScript

// 密码输入框组件
class PasswordInput {
	/**
	 * 构造函数
	 * @param {String} constainer_selector 密码输入框父(容器)元素的样式选择器
	 * @param {Function} toggleCallback  密码显示隐藏回调函数
	 */
	constructor(constainer_selector, toggleCallback) {
		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();
			
			// 添加 input 事件
			this._inputElem.addEventListener('input', this.inputHandle);
		}

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

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

	// 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 stop = oldPwd.length - val.length + cursorIndex; // 用户删除的字符串长度加光标的当前的位置,计算得出删除字符串的最后一个字符的位置
				const del_string = oldPwd.substring(cursorIndex, stop); // 获取用户删除的字符串
				newPwd = oldPwd.replace(del_string, ''); // 将旧的真实密码中对应的删除字符串替换为'',实现对真实密码的删除操作
			} else {
				const reg = /[^●]/.exec(val); // 获取虚假密码中新增的密码字符
				newPwd = this.insertStr(oldPwd, reg.index, reg[0]); // 将用户新输入的字符插入旧的真实密码
				this.cursorMove(this._inputElem, reg.index + 1); // 设置光标的位置
			}
		} 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();
		}
	}
}

使用方法:

<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">
    <input
      type="text"
      :value="hideValue"
      @input="input"
      ref='password-input'
      :placeholder="placeholder"
    >
    <div class="btn-show" @click="isShow = !isShow">
      <img v-if="isShow" src="@/assets/pwd-show.png" />
      <img v-else src="@/assets/pwd-hide.png" />
    </div>
  </div>
</template>
<script>
export default {
  name: 'password-input',
  props: {
    modelVal: String,
    placeholder: {
      type: String,
      default: '请输入'
    }
  },
  model: {
    prop: 'modelVal', //指向props的参数名
    event: 'input' //事件名称
  },
  data() {
    return {
      isShow: false,
      hideValue: ''
    };
  },
  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, '●');
      }
    },
    input() {
      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();
      }
    }
  }
};
</script>

<style lang="less" scoped>
.password-input {
  position: relative;
  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>

Vue 3.x

组件:

<template>
  <div class="password-input">
    <input
      type="text"
      :value="hideValue"
      @input="input"
      ref='password-input'
      :placeholder="placeholder"
    >
    <div class="btn-show" @click="isShow = !isShow">
      <img v-if="isShow" src="@/assets/pwd-show.png" />
      <img v-else src="@/assets/pwd-hide.png" />
    </div>
  </div>
</template>
<script>
export default {
  name: 'password-input',
  props: {
    modelVal: String,
    placeholder: {
      type: String,
      default: '请输入'
    }
  },
	emits: ['update:modelVal'],
  data() {
    return {
      isShow: false,
      hideValue: ''
    };
  },
  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, '●');
      }
    },
    input() {
      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('update:modelVal', 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();
      }
    }
  }
};
</script>

<style lang="less" scoped>
.password-input {
  position: relative;
	box-sizing: border-box;

	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>

使用方法:

<TheInputPassward v-model:modelVal="val" />
上一篇:Qt国际化翻译(中英切换)步骤:可子界面翻译


下一篇:第238天学习打卡(知识点回顾 阻塞队列)