使用meter元素实现密码强度效果(含密码强度计算算法)
实现效果
注册时候密码输入框显示密码强度是个很常见的交互效果,这种效果实现最好的方法一定是使用 HTML<meter>
元素,无论是跟随强度的 UI 色值变化,还是弱中强的文字提示,都是可以使用纯 CSS 实现,无需 JS 去控制 DOM 的色值与尺寸变化。
先看最终效果:http://code.wubin.work/code/html/meter-password-strong
接下来一步一步带大家了解具体的实现过程,先从了解<meter>
元素开始。
meter 元素基本特性与效果
单词 meter 直译意思是“计量器”,“计量表”,因此,在 Web 中,任何与丈量相关,同时需要分阶段提示的场景都非常使用使用<meter>
元素。
例如汽车油箱剩余油量,降雨量,风度,温度,游戏中人物角色的血量等。
先看下面一段代码:
生命值:<meter min="0" max="100" low="30" hight="60" optimum="80" value="20"></meter>
生命值:<meter min="0" max="100" low="30" hight="60" optimum="80" value="50"></meter>
生命值:<meter min="0" max="100" low="30" hight="60" optimum="80" value="70"></meter>
点击查看示例:http://code.wubin.work/code/html/meter-password-strong/test1.html
其中出现了 6 个 HTML 属性,正好涵盖了<meter>
元素所有常用属性。
属性简介
min
和max
属性表示数值范围,默认是 0-1,也就是如果min
属性如果不设置,则认为是 0,max
属性不设置则认为是 1。value
表示当前的值,默认是 0。low
和high
是比较特殊的 HTML 属性,目前仅出现在<meter>
元素上,表示警戒值,其中low
表示过低警戒值,high
表示过高警戒值。但是,大家务必注意,过低还是过高是否需要警戒还需要一个属性进行判定,那就是
optimum
属性。optimum
属性表示最佳值,用来决定过低和过高值属于正常还是异常,这个属性很重要,也是<meter>
元素学习的难点。
optimum 属性
optimum 属性的作用表现描述如下:
如果optimum
属性值在low
和high
之间,则说明low
和high
都是警戒值,只有在这个区间范围的值才是正常的,因此,最终的色值状态只会有两个,即橙色警戒和绿色正常。
代码示意:
生命值:<meter max="100" low="30" high="60" optimum="50" value="20"></meter>
生命值:<meter max="100" low="30" high="60" optimum="50" value="50"></meter>
生命值:<meter max="100" low="30" high="60" optimum="50" value="70"></meter>
效果如下所示,最多只会出现两种颜色。
http://code.wubin.work/code/html/meter-password-strong/test1.html
如果optimum
属性值比low
还要小,则说明 low 并不是警戒线,反而是推荐线,也就是越小反而越高,此时,low-high 之间的范围就属于警戒,而超过 high 的值就属于危险了。
于是,最终<meter>
元素可以有 3 段色值状态,HTML 代码示意:
生命值:<meter max="100" low="30" high="60" optimum="20" value="20"></meter>
生命值:<meter max="100" low="30" high="60" optimum="20" value="50"></meter>
生命值:<meter max="100" low="30" high="60" optimum="20" value="70"></meter>
如果optimum
属性值比high
还要大,也会出现 3 段色值状态,此时,大于 high 的值会被认为是正常的,因此表现为绿色,这个效果,就是一开始那个例子所演示的那个效果,代码和截图效果是这样的:
生命值:<meter max="100" low="30" high="60" optimum="80" value="20"></meter>
生命值:<meter max="100" low="30" high="60" optimum="80" value="50"></meter>
生命值:<meter max="100" low="30" high="60" optimum="80" value="70"></meter>
回到本文这里,由于密码强度越强越好,因此,很显然,需要设置optimum
属性值比high
还要大。
但是,我们需要的是明显分段的效果,而浏览器默认的<meter>
元素效果就是个色条,不符合产品需求,那有没有什么办法自定义 UI 呢?
meter 元素的样式自定义
在现代浏览器下,<meter>
元素提供了若干伪元素,可以让我们对<meter>
元素进行样式自定义。
其中,Chrome 浏览器和 Safari 浏览器可以使用的伪元素非常丰富,Firefox 浏览器相对少一些。
这里,主要以 Chrome 浏览器示意。
所有可以设置<meter>
元素的选择器包括下面这些:
- meter 元素自身选择器;
- ::-webkit-meter-inner-element {}
- ::-webkit-meter-bar {} 灰色背景条。
- ::-webkit-meter-even-less-good-value {} 红色。
- ::-webkit-meter-optimum-value {} 橙色。
- ::-webkit-meter-suboptimum-value {} 绿色。
各个伪元素所对应的 DOM 层级结构如下图所示:
使用字符图形表示是这样的:
meter
|-- ::-webkit-meter-inner-element
|-- ::-webkit-meter-bar
|-- ::-webkit-meter-even-less-good-value -|
|-- ::-webkit-meter-suboptimum-value -|--> 只会同时出现1个
|-- ::-webkit-meter-optimum-value -|
所以,我们就可以使用上面的伪元素实现我们想要的 UI 效果了。
然而,事情并没有预想的那么简单。
- 密码强度如何确定?
- 表示 3 个颜色的伪元素最多只会出现 1 个,而我们的目标效果却是 3 色同时显示,另外,默认的色值是连续的,而需要的效果是分段的,该怎么实现呢?
- 强度色块下面还有“弱 中 强”的文字提示,这个布局效果又该如何实现呢?
关于这些实现难点,我们一个一个来看下,其中不乏一些有创意的实现技巧。
密码强度的计算
密码强度的计算直接使用开源的公认的算法就可以了,比方说
zxcvbn:https://github.com/dropbox/zxcvbn
使用也相当简单,引入然后执行,示意:
<script src="https://cdn.bootcdn.net/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"></script>
<script>
let objResult = zxcvbn('your password');
</script>
其中 objResult 包含下面这些属性:
{
guesses: Number
guesses_log10: Number
score: Number (0-4)
crack_times_seconds: Object (不同运算力下的破解时间,数值展示)
crack_times_display: Object (不同运算力下的破解时间,字符描述展示)
sequence: Array 验证序列
feedback: 反馈与建议
calc_time: zxcvbn 计算此密码强度花费的时间,单位是毫秒 ms
}
其中,与密码强度直接相关的是:guesses、guesses_log10 和 score。
其中,score 范围是 0-4,数值越大,密码越安全,虽然也能解决强度的判断,但是体现在<meter>
元素上就没有了丰度的 value 变化,视觉效果也不咋地。
因此,在本文所描述的场景下,我们使用 guesses_log10 作为我们的强度判断值。
根据我的测试,guesses_log10 的值在 12 以上,密码强度就已经很 Strong 了,考虑到弱中强三种状态的色条范围是三等分的,因此,最终的<meter>
元素的高低值如下所示:
<meter min="0" max="12" low="4" high="8" optimum="10"></meter>
相关 JS 代码则是这样子的:
meter.value = zxcvbn('paddworld').guesses_log10;
剩下的视觉表现的工作全部都交给 CSS 好了。
最终样式实现的细节
背景条的样式
首先,我们确定个尺寸,假设 长度是 120px,高度是 12px:
::-webkit-meter-bar {
width: 120px;
height: 12px;
}
浏览器默认的边框效果是不需要的,然后为了兼容性,需要统一背景色,于是有:
::-webkit-meter-bar {
width: 120px; height: 12px;
border: 0;
background: #eee;
}
Firefox 浏览器可以使用 ::-moz-meter-bar 伪元素设置。
色带的实现
浏览器默认的状态都是纯色,从左纯到右,想要变成一个一个的色带,不少人的想法会是叠加,也就是几个<meter>
元素叠加在一起,其实不需要这么麻烦的,我们使用 CSS 渐变模拟就好了。
例如,进入橙色状态的时候,把前 1/3 部分改成红色(原来是橙色),这样,当状态条从红色变成橙色的时候,就像是无缝添加的,而不是突然替换,绿色这部分也是类似的。
代码示意:
::-webkit-meter-even-less-good-value {
background: red;
}
::-webkit-meter-suboptimum-value {
background: linear-gradient(to right, red 40px, orange 0);
}
::-webkit-meter-optimum-value {
background: linear-gradient(to right, red 40px, orange 0 80px, green 0);
}
以::-webkit-meter-optimum-value
伪元素举例,浏览器默认状态下,这个伪元素是纯绿色,这里使用 CSS 渐变重新实现之后,就是 红-橙-绿 三段,很好地模拟了三态强度效果。
中间镂空分隔
上面的效果,各个色块是紧密相连的,但是最终需要的效果是彼此之间有间隙,这个该如何实现呢?
方法其实挺多的,我这里选择使用遮罩实现,在色块的容器元素上绘制一个镂空渐变作为遮罩图像:
::-webkit-meter-bar {
-webkit-mask: linear-gradient(to right,
red 39px,
transparent 0 41px,
orange 0 79px,
transparent 0 81px,
green 0
);
}
文字左中右对齐实现
接下来就是“弱中强”三个文字的效果实现了。
有人会想到使用一个<span>
元素和<meter>
元素包在一起再重定位,这个虽然也能实现,但是 HTML 就啰嗦了,以后也不太好维护。
实际上,直接使用<meter>
元素就可以实现,方法就是::after
伪元素。
meter::after {
content: '弱中强';
}
不过有几个问题,首先,文字占据的尺寸空间,影响了和输入框的垂直对齐,这个好办,设置为绝对定位就可以了:
meter::after {
content: '弱中强';
position: absolute;
}
但是,“弱中强”三个字要分别在 3 个颜色片段的中间,这个对齐该怎么实现呢?
我们可以让伪元素宽度充满<meter>
元素,给文字中间增加空格、末尾增加一串长长的连续英文字符实现:
meter {
position: relative;
}
meter::after {
content: '弱 中 强 aaaaaaaaaaaaaaaaaaaaaa';
position: absolute;
left: 0; right: 0;
/* 两端对齐 */
text-align: justify;
/* 隐藏 aaaaaaaaaaaaaaaaaaaaaa */
height: 20px;
line-height: 20px;
overflow: hidden;
}
为什么后面需要一段莫名其妙的'aaaaaaaaaaaaaaaaaaaaaa'
呢,因为两端对齐效果的实现,处理字符之间有空格,还需要内容超过1行,而连续英文字符默认是不会换行的,因此会作为整体在第 2 行显示,从而让第一行的 “弱中强”三个字两端对齐。
但是如此文字就会在左边,我们要求的是在中间位置。
所以,需要修改下left
和top
位置微调下(中文字符占据宽度是 1em):
meter {
position: relative;
}
meter::after {
content: '弱 中 强 aaaaaaaaaaaaaaaaaaaaaa';
position: absolute;
left: calc(20px - .5em);
right: calc(20px - .5em);
/* 两端对齐 */
text-align: justify;
/* 隐藏 aaaaaaaaaaaaaaaaaaaaaa */
height: 20px;
line-height: 20px;
overflow: hidden;
}
不同文字不同颜色实现
详见:“CSS3下的渐变文字效果实现”。这里使用background-clip:text
加 linear-gradient 渐变背景实现的。
meter::after {
-webkit-text-fill-color: transparent;
background-image: linear-gradient(to right,
red 39px,
transparent 0 41px,
orange 0 79px,
transparent 0 81px,
green 0
);
-webkit-background-clip: text;
}
Safari 浏览器
原本以为safari浏览器不支持,后来研究了一下,发现原来只需要设置外面的meter 元素边框尺寸为0或none就可以触发伪元素重定义了,也就是:
meter {
border: 0;
}
最终,使用css变量优化一下渐变以及尺寸,可以查看最终结果:
http://code.wubin.work/code/html/meter-password-strong/final.html
兼容性
当然,正如一开始所提到的,<meter>
元素不仅可以用来表示密码强度。在 Web 中,所有与丈量、测量相关的场景,都可以考虑使用这一个 HTML 元素。
有人可能会纠结 IE 浏览器的问题。
毕竟<meter>
元素的兼容性是这样子的:
https://caniuse.com/?search=meter
一种方法是使用polyfill,我这里找了个项目:https://github.com/fisker/meter-polyfill
不过这个 polyfill 项目我自己没试过,所以质量如何并不清楚。
从我自己的角度讲,如果我是遇到这样的场景,我会说服产品直接放弃 IE浏览器,就不用搞什么所谓的密码强度了,本来这种密码强度就是锦上添花的功能,没有的话,也不影响功能。
况且现在 IE 浏览器占比其实非常低了,3%都不到,我不知道其他公司产品怎样的,以及我们开发产品总是要面向未来的,你不能说我当下还有一些 IE 浏览器用户,你就放弃了未来,想想看,现在项目中还有兼容 IE8 浏览器的代码,是不是想吐。
当然,如果产品是个直溜子说服不通,以为工程师就是想偷懒。
那我觉得专门给IE做一个特殊的处理,其实也不需要多大的成本,回头哪天 IE 嗝屁了直接移除就好。