一般情况下,如果要实现密码输入框,采用浏览器原生的密码输入框是很好的选择。比如登录界面,使用浏览器原生的密码输入框,用户就可以使用浏览器自身的‘’记住密码“功能,不用每次登录都需要手动输入账号密码。但是呢!有某些情况下,只需要密码框的密码显示/隐藏功能。如果使用浏览器原生的密码框,“记住密码”功能的弹窗,就会造成很大的不便。所以,在这种情况下,就需要模拟实现密码框的功能了。
密码框的功能解析
在实现密码框之前,首先要了解密码框的功能。
密码框的功能有:
- 文本输入功能
- 密码显示隐藏功能
文本输入功能
首先,密码框能像普通的文本输入框一样,进行密码的输入修改。此外,用户输入的密码不能明码显示出来,要转化成特殊字符(●
)显示。
要实现这一点,需要做两个步骤:
- 使用普通的文本输入框来进行密码输入
- 给文本输入框添加
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" />