vue自定义密码输入框解决浏览器自动填充密码的问题

news2024/10/7 12:19:59

浏览器对于type="password"的输入框会自动填充密码,但有时出于安全或者其他原因,我们不希望浏览器记住并自动填充密码。通过网上查到的一些解决方案,可以总结出以下几种解决方案(主要用edge浏览器进行测试):

  1. 通过autocomplete="off"/autocomplete="new-password"来关闭浏览器自动填充密码的功能, 但某些对于浏览器像edge,firfox等,这种方法并不起作用
  2. 通过type="text"来解决,当focus时,通过js将type="text"改为type="password"
<input type="text" onfocus="this.type='password'">

但同样对某些浏览器不起作用,如edge,在点击输入框时,仍会自动弹出填充密码的提示框。

  1. 某些浏览器可能只会识别第一个type="password"的输入框,所以可以在前面添加一些隐藏的type="password"的输入框,来解决这个问题。
<form style="display:none">
  <input type="password">
</form>
<input type="password" style="display:none">
<input type="password">

但同样并不是总是有效,拿edge测试时即使前几个密码输入框没有隐藏,最后一个输入框也会自动填充密码,如图:

  1. 通过readonly属性来解决,初始化时将readonly设置为true,通过setTimeout来延时设置readonlyfalse
<input id="passwordInput" type="password" readonly>
setTimeout(() => {
    document.getElementById('passwordInput').removeAttribute('readonly')
}, 100)

但同样并非总是有效,拿edge测试时,虽然点击输入框时并没有弹出填充密码的提示框,但是在输入框中输入密码然后退格到输入框为空时,又会重新弹出填充密码的提示框。

上述几种方法除了会弹出填充密码的提示框外,在页面跳转或刷新时(如edge浏览器),都会弹出保存密码的提示框,如图:

当然,应该还会有其他解决方案我暂时还没找到,如果有的话,欢迎留言。

自定义密码输入框组件解决方案

在尝试了上述几种解决方案后,发现效果都不是很好,所以我感觉只有让inputtype属性始终为password,才能更有效的解决这个问题。可以考虑自定义一个密码输入框组件,通过某些方法去改变input的值的显示方式,来达到隐藏密码的效果。
目前想出了两种方法:一个是不改变input的值,仅仅隐藏input的内容,用另一个容器去显示密码或者显示*;另一个是将实际密码存在另一个变量中,将inputvalue值改成*来显示。

方案一

可以用两个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)
  }
}

需要注意以下几点:

  1. 监听select事件不能用input自带的onselect@select,因为这只会在鼠标松开时触发,并不能实时相应选取区域的变化。所以要监听selectionchange事件。注意selectionchange事件在没选中内容时也会触发。
  2. 由于相比方案一显示了光标,光标的位置会受到实际字符宽度的影响,所以要使星号与其他字符宽度相等,必须使用如monospace之类的等宽字体,且必须阻止中文字符的输入。
  3. 修改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进行计算字体宽度。

虽然最终实现了目标效果,不过逻辑上还是稍微复杂了点。

完整代码在: GitHub - 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), 只能通过inputselectionStartselectionEnd可以拿到当前选中区域的起始和结束位置。
  • 由于输入框内实际显示的是星号,所以复制时若不处理则复制的也是星号,所以需要监听复制事件,将实际密码写入剪贴板。剪贴板通过e.clipboardData || window.clipboardData获取。

相比于方案二,这种方法无需要求一定要等宽字体,也无需另外去处理选中内容的事件,唯一多出的地方就是对输入框实际值的处理,包括输入和复制,而这里的逻辑显然比方案二中修改样式容易的多。

效果上跟方案二基本差不多,而且没有长度限制,这里用this.passwordDisplay = '\u2022'.repeat(this.value.length)把星号改成了圆点,如下:

完整代码在: GitHub - 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可以通过一些转换工具来实现,这里推荐一个网站: Free SVG Converter

<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>

效果如下:

完整代码在: GitHub - lxmghct/my-vue-components
src/components/PasswordInput/PasswordInput2.vue 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/856867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

玩一玩通义千问Qwen开源版,Win11 RTX3060本地安装记录!

大概在两天前&#xff0c;阿里做了一件大事儿。 就是开源了一个低配版的通义千问模型--通义千问-7B-Chat。 这应该是国内第一个大厂开源的大语言模型吧。 虽然是低配版&#xff0c;但是在各类测试里面都非常能打。 官方介绍&#xff1a; Qwen-7B是基于Transformer的大语言模…

[JavaScript游戏开发] Q版地图上让英雄、地图都动起来

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 第五章 Q版地图上让英雄、地图都动起来…

数据结构—图的遍历

6.3图的遍历 遍历定义&#xff1a; ​ 从已给的连通图中某一顶点出发&#xff0c;沿着一些边访问遍历图中所有的顶点&#xff0c;且使每个顶点仅被访问一次&#xff0c;就叫作图的遍历&#xff0c;它是图的基本运算。 遍历实质&#xff1a;找每个顶点的邻接点的过程。 图的…

数据结构笔记--链表经典高频题

前言 面经&#xff1a; 针对链表的题目&#xff0c;对于笔试可以不太在乎空间复杂度&#xff0c;以时间复杂度为主&#xff08;能过就行&#xff0c;对于任何题型都一样&#xff0c;笔试能过就行&#xff09;&#xff1b;对于面试&#xff0c;时间复杂度依然处在第一位&#xf…

量化交易可视化(9)-热力图

热力图的含义 热力图是一种用颜色编码数据密度的二维图表。它的含义是通过不同颜色的渐变来显示数据的相对密度或值的大小。 热力图通常用于可视化矩阵或二维表格数据&#xff0c;其中每个单元格的值被映射到一个颜色&#xff0c;从而形成一个色阶。较小的值通常用较浅的颜色表…

许多智能算法并不智能(续)

许多智能算法被认为并不智能&#xff0c;主要是因为它们在某些方面仍然存在一些限制。以下是一些常见的原因&#xff1a; 缺乏常识和理解能力&#xff1a;当前的智能算法主要依赖于大量的数据和模式识别来做出决策&#xff0c;但它们通常缺乏对世界的常识和深层理解。这意味着它…

视觉大模型的全面解析

前言 本文主要围绕Foundational Models&#xff0c;即基础模型&#xff08;通过自监督或半监督方式在大规模数据上训练的模型&#xff0c;可以适应其它多个下游任务。&#xff09;这个概念&#xff0c;向大家全面阐述一个崭新的视觉系统。例如&#xff0c;通过 SAM&#xff0c;…

nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6引起的online表单开发的数据库导入出错解决

更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 nbcio-boot因升级…

【雕爷学编程】Arduino动手做(01)---干簧管传感器模块2

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

接龙序列(14届)

对于一个长度为 K 的整数数列&#xff1a;A1,A2,...,AK&#xff0c;我们称之为接龙数列当且仅当 Ai 的首位数字恰好等于 Ai−1的末位数字 (2≤i≤K2≤i≤K)。 例如 12,23,35,56,61,11 是接龙数列&#xff1b;12,23,34,56 不是接龙数列&#xff0c;因为 56 的首位数字不等于 34…

matplotlib 笔记:plot 折线图

1 基本用法 import numpy as np import matplotlib.pyplot as plt x np.linspace(0,10,50) y np.tan(x)**2 np.cos(x)plt.plot(x, y) plt.show() 2 linestyle 折线图类型 import numpy as np import matplotlib.pyplot as pltx np.linspace(0,10,50) y np.tan(x)**2 np…

node配置Web 服务器

1、什么是 Web 服务器 Web服务器一般指网站服务器&#xff0c;是指驻留于因特网上某种类型计算机的程序&#xff0c;Web服务器的基本功能就是提供Web信息浏览服务。 它只需支持HTTP协议、HTML文档格式及URL&#xff0c;与客户端的网络浏览器配合。 大多数 web 服务器都支持服…

如何优化测试用例?

在我们日常测试工作中&#xff0c;编写测试用例往往花费较多时间&#xff0c;而且设计的测试用例冗杂和不完整&#xff0c;从而造成用例执行检查不完整&#xff0c;效率低下&#xff0c;不能及时发现项目缺陷。 因此提高测试用例编写和执行效率迫在眉睫&#xff0c;一般来说&am…

Dubbo3.0 Demo

将SpringBoot工程集成Dubbo 1.创建父工程 2.创建子工程consumer&#xff0c;provider 3.初始化工程 4.引入依赖 在provider和consumer中引入dubbo依赖 <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</a…

文心一言 VS 讯飞星火 VS chatgpt (73)-- 算法导论7.1 4题

四、如何修改 QUICKSORT&#xff0c;使得它能够以非递增序进行排序? 文心一言&#xff1a; 快速排序&#xff08;Quicksort&#xff09;是一种常用的排序算法&#xff0c;基于分治策略进行设计。默认情况下&#xff0c;快速排序会以递增序进行排序。若想修改快速排序以实现非…

基于星火和Gradio的聊天机器人

星火大模型官网&#xff1a;https://xinghuo.xfyun.cn/ 1 创建虚拟环境&#xff08;windows&#xff09; conda create -n Gradio python3.8 pip install gradio 中间遇到os报错&#xff0c;解决方案&#xff1a; pip install aiofiles23.2.1 2 代码 SparkDesk.py&#xff1a…

Android Studio跳过Haxm打开模拟器

由于公司权限限制无法安装Haxm&#xff0c;这个时候我们可以试试Arm相关的镜像去跳过Haxm运行模拟器。解决方案&#xff1a;安装API27以下的Arm Image. #ifdef __x86_64__if (sarch "arm64" && apiLevel >28) {APANIC("Avds CPU Architecture %s i…

linux_常用命令

一、日常使用命令/常用快捷键命令 开关机命令 1、shutdown –h now&#xff1a;立刻进行关机 2、shutdown –r now&#xff1a;现在重新启动计算机 3、reboot&#xff1a;现在重新启动计算机 4、su -&#xff1a;切换用户&#xff1b;passwd&#xff1a;修改用户密码 5、logou…

使用IIS服务器部署Flask python Web项目

参考文章 ""D:\Program Files (x86)\Python310\python310.exe"|"D:\Program Files (x86)\Python310\lib\site-packages\wfastcgi.py"" can now be used as a FastCGI script processor参考文章 请求路径填写*&#xff0c;模块选择FastCgiModule&…

web-xss-dvwa

目录 xss&#xff08;reflected&#xff09; low medium high xss(store) low medium high xss(dom) low medium high xss&#xff08;reflected&#xff09; low 没有什么过滤&#xff0c;直接用最普通的标签就可以了 http://127.0.0.1/DVWA-master/vulnerabili…