vue自定义密码输入框解决浏览器自动填充密码的问题
pre { overflow-y: auto; max-height: 300px }
img { max-width: 500px; max-height: 300px }
问题描述
浏览器对于type="password"
的输入框会自动填充密码,但有时出于安全或者其他原因,我们不希望浏览器记住并自动填充密码。通过网上查到的一些解决方案,可以总结出以下几种解决方案(主要用edge浏览器进行测试):
- 通过
autocomplete="off"
/autocomplete="new-password"
来关闭浏览器自动填充密码的功能, 但某些对于浏览器像edge,firfox等,这种方法并不起作用 - 通过
type="text"
来解决,当focus时,通过js将type="text"
改为type="password"
。
<input type="text" onfocus="this.type='password'">
但同样对某些浏览器不起作用,如edge,在点击输入框时,仍会自动弹出填充密码的提示框。
- 某些浏览器可能只会识别第一个
type="password"
的输入框,所以可以在前面添加一些隐藏的type="password"
的输入框,来解决这个问题。
<form style="display:none">
<input type="password">
</form>
<input type="password" style="display:none">
<input type="password">
但同样并不是总是有效,拿edge测试时即使前几个密码输入框没有隐藏,最后一个输入框也会自动填充密码,如图:
- 通过
readonly
属性来解决,初始化时将readonly
设置为true
,通过setTimeout
来延时设置readonly
为false
。
<input id="passwordInput" type="password" readonly>
setTimeout(() => {
document.getElementById('passwordInput').removeAttribute('readonly')
}, 100)
但同样并非总是有效,拿edge测试时,虽然点击输入框时并没有弹出填充密码的提示框,但是在输入框中输入密码然后退格到输入框为空时,又会重新弹出填充密码的提示框。
上述几种方法除了会弹出填充密码的提示框外,在页面跳转或刷新时(如edge浏览器),都会弹出保存密码的提示框,如图:
当然,应该还会有其他解决方案我暂时还没找到,如果有的话,欢迎留言。
自定义密码输入框组件解决方案
在尝试了上述几种解决方案后,发现效果都不是很好,所以我感觉只有让input
的type
属性始终为password
,才能更有效的解决这个问题。可以考虑自定义一个密码输入框组件,通过某些方法去改变input
的值的显示方式,来达到隐藏密码的效果。
目前想出了两种方法:一个是不改变input
的值,仅仅隐藏input
的内容,用另一个容器去显示密码或者显示*
;另一个是将实际密码存在另一个变量中,将input
的value
值改成*
来显示。
方案一
可以用两个input
来实现,父容器是relative
定位,两个input
都是absolute
,一个实际的输入框位于上层,设置为透明,另一个用于显示星号的输入框位于下层。
<div class="container">
<input v-model="passwordDisplay">
<input
v-model="password"
class="password"
@input="passwordDisplay = password.replace(/./g, '*')">
</div>
<style scoped>
.container {
position: relative;
}
.container input {
position: absolute;
left: 0;
top: 0;
font-size: 12px;
}
.password {
opacity: 0;
}
</style>
效果如下图所示:
确实没有弹出密码填充的对话框,但样式上并不是很满意。因为实际的输入框被设置成了透明,且在密码显示框之上,所以光标无法显示出来,且无法进行选中一部分内容。
方案二
跟方案一差不多的方式,用input
来接收用户输入的密码,但仅改变输入内容的透明度, 由于在opacity
为0的情况下设置光标颜色无效,所以要将方案一中的opacity: 0
改为:
.password {
color: transparent;
background-color: transparent;
caret-color: #000; /* 光标颜色 */
}
但是这会有个问题,选中一部分内容时,会导致透明的内容选中后显现出来,如图所示:
这种情况下可以考虑监听选中事件,当选中一部分内容时,将后面的星号也选中,同时通过::selection
伪类来设置选中的内容的背景色,让两个选中的内容颜色一致。要实现这种效果,input
显然做不到修改部分内容的背景色,所以可以考虑用span
代替input
,向其innerHTML
中插入带背景色的span
:
<div class="container">
<span
ref="passwordInputDisplay"
class="password password-input__behind"
/>
<input
v-model="password"
class="password password-input__front"
@focus="isActive = true"
@blur="isActive = false"
@input="passwordDisplay = password.replace(/./g, '*')">
</div>
<style scoped>
::selection {
background-color: #409eff;
}
.container {
position: relative;
}
.password {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 12px;
font-family: monospace; /* 必须用等宽字体 */
}
.password-input__behind {
text-align: left;
z-index: 1;
}
.password-input__front {
color: transparent;
background-color: transparent;
caret-color: #000;
z-index: 2;
}
export default {
props: {
value: {
type: String,
default: ''
}
},
methods: {
handleInput (e) {
// 删除非法字符(只保留code>=32且code<=126的字符)
const value = e.target.value
const newValue = value.replace(/[^\x20-\x7E]/g, '')
if (newValue !== value) {
this.password = newValue
}
// 发布input事件,从而修改props中的value值
this.$emit('input', this.password)
}
},
created() {
this.selectionEvent = () => {
const display = this.$refs.passwordInputDisplay
display.style.zIndex = 1
display.innerHTML = this.passwordDisplay
if (!this.isActive) { return }
const selection = window.getSelection()
// 如果选中的内容不为空, 则由passwordInputDisplay显示
if (!selection.toString()) { return }
const input = this.$refs.passwordInput
const start = input.selectionStart
const end = input.selectionEnd
const highlightString = '<span style="background-color: #409eff; color: #fff;">' + this.passwordDisplay.slice(start, end) + '</span>'
display.innerHTML = this.passwordDisplay.slice(0, start) + highlightString + this.passwordDisplay.slice(end)
display.style.zIndex = 4
}
document.addEventListener('selectionchange', this.selectionEvent)
},
beforeDestory() {
document.removeEventListener('selectionchange', this.selectionEvent)
}
}
需要注意以下几点:
- 监听
select
事件不能用input
自带的onselect
或@select
,因为这只会在鼠标松开时触发,并不能实时相应选取区域的变化。所以要监听selectionchange
事件。注意selectionchange
事件在没选中内容时也会触发。 - 由于相比方案一显示了光标,光标的位置会受到实际字符宽度的影响,所以要使星号与其他字符宽度相等,必须使用如
monospace
之类的等宽字体,且必须阻止中文字符的输入。 - 修改
innerHtml
后需要改变密码显示框的z-index
,否则仍然会被input
中选中的内容覆盖。
效果如下图所示:
这里还有个问题,当输入内容超过了input
的长度,显示上就会出现错误,可以考虑根据字体宽度计算出最大容纳的字符个数,阻止过多字符的输入。也可以在光标移动时同时移动后面的span
,不过逻辑太过复杂没必要。
const width = this.$refs.passwordInput.clientWidth - 20 // 20为padding
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.font = '16px monospace'
const fontWidth = ctx.measureText('A').width
this.maxLength = Math.floor(width / fontWidth)
这里用的是canvas
进行计算字体宽度。
虽然最终实现了目标效果,不过逻辑上还是稍微复杂了点。
完整代码在: https://github.com/lxmghct/my-vue-components
src/components/PasswordInput/PasswordInput1.vue
方案三
只使用一个input
,另外设置一个变量去保存真实密码。这种方法比上述方法逻辑上要稍微简单一些,唯一需要注意的就是当输入框中显示为星号时,如何区分哪些是新输入的内容,因为会有鼠标选中一段内容再删除或输入、粘贴的操作,而新输入的内容中也可能包含星号,所以不能处理的过于简单。最后采用的是监听selectionchange
事件来随时更新光标所在位置,从而区分新输入的内容。
<input
ref="passwordInput"
v-model="passwordDisplay"
autocomplete="off"
@focus="isActive = true"
@blur="isActive = false"
@input="handleInput"
>
export default {
methods: {
handleInput () {
// 获取新输入的字符
const tempEnd = this.passwordDisplaylength - (this.password.length - thisselection.end)
const newStr = this.passwordDisplay.slic(this.selection.start, tempEnd)
// 更新输入框的值
const currentPosition = this.$refspasswordInput.selectionStart
this.password = this.password.slice(0,Math.min(this.selection.start,currentPosition)) + newStr + this.passwordslice(this.selection.end)
this.selection.start = currentPosition
this.selection.end = currentPosition
this.$emit('input', this.password)
}
},
created () {
this.selectionEvent = () => {
if (!this.isActive) { return }
const input = this.$refs.passwordInput
this.selection = {
start: input.selectionStart,
end: input.selectionEnd
}
}
this.copyEvent = (e) => {
if (!this.isActive) { return }
const clipboardData = e.clipboardData || window.clipboardData
clipboardData.setData('text', this.password.slice(this.selection.start, this.selection.end))
e.preventDefault()
}
document.addEventListener('selectionchange', this.selectionEvent)
document.addEventListener('copy', this.copyEvent)
},
beforeDestroy () {
document.removeEventListener('selectionchange', this.selectionEvent)
document.removeEventListener('copy', this.copyEvent)
}
}
有几点需要注意:
- 输入框中选定的内容的起始和结束位置无法通过
window.getSelection().anchorOffset
等参数获取(window.getSelection()
的几个offset都是0), 只能通过input
的selectionStart
和selectionEnd
可以拿到当前选中区域的起始和结束位置。 - 由于输入框内实际显示的是星号,所以复制时若不处理则复制的也是星号,所以需要监听复制事件,将实际密码写入剪贴板。剪贴板通过
e.clipboardData || window.clipboardData
获取。
相比于方案二,这种方法无需要求一定要等宽字体,也无需另外去处理选中内容的事件,唯一多出的地方就是对输入框实际值的处理,包括输入和复制,而这里的逻辑显然比方案二中修改样式容易的多。
效果上跟方案二基本差不多,而且没有长度限制,这里用this.passwordDisplay = '\u2022'.repeat(this.value.length)
把星号改成了圆点,如下:
完整代码在: https://github.com/lxmghct/my-vue-components
src/components/PasswordInput/PasswordInput2.vue
密码显示与隐藏
点击眼睛图标,切换密码的显示与隐藏状态。
export default {
watch: {
value () {
this.updatePasswordDisplay()
},
showPassword () {
this.updatePasswordDisplay()
}
},
methods: {
updatePasswordDisplay () {
if (this.showPassword) {
this.passwordDisplay = this.value
} else {
// this.passwordDisplay = '*'.repeat(this.value.length)
this.passwordDisplay = '\u2022'.repeat(this.value.length) // 圆点
}
}
}
}
眼睛图标可以用图标库或者导入图片,我这里用的是svg
,眼睛图标的svg
可以通过一些转换工具来实现,这里推荐一个网站: https://picsvg.com/
<div class="password-input__eye-wrap">
<div
class="password-input__eye"
@click="showPassword = !showPassword"
>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="58.000000pt" height="50.000000pt" viewBox="0 0 58.000000 50.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,50.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M228 390 c-61 -19 -148 -96 -148 -130 0 -21 61 -87 103 -110 50 -29
127 -32 173 -8 39 21 114 98 114 118 0 19 -74 97 -111 115 -36 19 -98 26 -131
15z m121 -40 c37 -18 91 -72 91 -90 0 -18 -54 -72 -91 -90 -70 -36 -138 -22
-206 43 -18 17 -33 38 -33 47 0 19 53 71 95 93 41 22 98 21 144 -3z"/>
<path d="M235 338 c-31 -18 -44 -40 -45 -75 0 -45 9 -62 42 -79 84 -43 168 60
106 130 -27 30 -74 41 -103 24z m79 -34 c20 -20 20 -68 0 -88 -35 -35 -104 -6
-104 44 0 50 69 79 104 44z"/>
</g>
</svg>
</div>
</div>
<style scoped>
.password-input__eye-wrap {
display: flex;
align-items: center;
justify-content: center;
}
.password-input__eye {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
效果如下:
完整代码在: https://github.com/lxmghct/my-vue-components
src/components/PasswordInput/PasswordInput2.vue
觉得有帮助的可以在github上点个star~
总结
通过将密码输入框的type
设置为text
,修改样式上的显示,来实现既可以让浏览器不自动填充密码,又可以隐藏密码的效果。
vue自定义密码输入框解决浏览器自动填充密码的问题的更多相关文章
- 解决chrome浏览器自动填充密码
chrome会自动填充密码,解决方法很简单 使用下面的参考代码即可: <input type="password" readonly οnfοcus="this.r ...
- 解决浏览器自动填充input
浏览器输入框自动填充解决办法 emmmmm:今天处理公司后台系统遇到的:登录页面浏览器保存账号密码后:浏览器会自动在其他页面进行填充:解决如下图: 浏览器会默认填充input type值为passwo ...
- Vue+element 解决浏览器自动填充记住的账号密码问题
我们在做form表单的时候,会发现,浏览器会自动的将我们之前保存的密码, 自动的填充到表单中input 为 type="password" 的框中 登录页面也就算了,但是注册页面就 ...
- chrome浏览器自动填充失效问题
现在浏览器带有自动填充,一般在input标签中增加autocomplete="off" 可以进行控制,off代表不填充,on代表填充,这个属性也可以放在form标签中,对所有的in ...
- google chrome浏览器自动填充解决方案
在chrome浏览器中,浏览器对于[1]type为password和text的.[2]带有name或者id属性的<input>标签会有自动填充表单功能,虽然会给用户记住密码带来一定的便利, ...
- chrome表单禁止自动填充密码
在用户登录成功以后,chrome会提示用户记住密码. 用户退出以后若前往注册.忘记密码页时会自动填充密码. 若不希望自动填充密码,只需在input里设置autoComplete="new-p ...
- input type=password 浏览器会自动填充密码的问题
解决办法是在form上或input上添加autoComplete="off"这个属性. form表单的属性如下所示: 但是这个解决方案在谷歌和火狐上均有bug,下面来一个一个解决. ...
- 如何禁止浏览器自动填充非登陆input的账号和密码?
发现浏览器填充密码的方式,那就是,找到页面上第一个type为password的input填充.发现了这个规律后,很自然的就想到了,是不是可以在真正的password前面加一个隐藏的password,形 ...
- 解决谷歌浏览器给输入框input自动填充密码问题
这时候我们可能会在 input上 加上 autocompleted="off" 这个属性来阻止input被自动填充. <input type="text" ...
- input placeholder 在chrome 浏览器自动填充时,背景色覆盖原有背景图片问题。
user-block-name, .user-block-pwd { margin-bottom: 10%; text-align: center; position: relative; } .us ...
随机推荐
- vue项目如何配置路由
vue3项目中如何配置路由 1.下载vue-router,在路由文件中引入相关依赖 import {createRouter,createWebHashHistory} from 'vue-route ...
- 转载一个很强大的GIT存储库学习链接
https://learngitbranching.js.org/?locale=zh_CN 可以按照步骤一步步学习相关的知识点
- nodejs批量重命名
const fs = require("fs"); // directory path let config = { affix: null, src: null, }; ...
- 代码版本管理git
git工作流程如下: 克隆 Git 资源作为工作目录. 在克隆的资源上添加或修改文件. 如果其他人修改了,你可以更新资源. 在提交前查看修改. 提交修改. 在修改完成后,如果发现错误,可以撤回提交并再 ...
- pom.xml配置资源过滤
<build> <!--设置资源过滤--> <resources> <resource> <directory>src/main/java& ...
- git练习网站(图形化版)
https://learngitbranching.js.org/?locale=zh_CN
- VisualVM无法运行,修改配置文件
在VisualVM安装位置下找到etc目录修改etc目录下的visualvm.conf文件 加入配置 参数 指定JDK或JRE路径,如 visualvm_jdkhome="C:\xxx\ ...
- 关于Unity 图片队列存储以及出列导致内存溢出的解决方案
图片虽然出列但是并没有销毁,所以..destroy !
- Python Type Hint中Optional[str]=None和str=None的区别
Python Type Hint中Optional[str]=None和str=None的区别 1 问题来源 在读到Fluent Python, 2ed Edition, P260时产生了一些疑问: ...
- layui 手册
https://layui.yii666.com/doc/modules/layer.html