密码强度效果最佳实现一定是HTML meter元素

这篇文章发布于 2021年11月14日,星期日,22:44,归类于 CSS相关, HTML相关。 阅读 5561 次, 今日 188 次 21 条评论

 

注册时候密码输入框显示密码强度是个很常见的交互效果,例如下面找的效果截图:

密码强度截图

这种效果实现最好的方法一定是使用 HTML <meter> 元素,无论是跟随强度的 UI 色值变化,还是弱中强的文字提示,都是可以使用纯 CSS 实现,无需 JS 去控制 DOM 的色值与尺寸变化。

一、先看实现效果

下面两张图就是最终在浏览器中实现的效果。

Chrome 浏览器下:

密码提示强弱

Safari 浏览器下:

Safari浏览器下截图

眼见为实,您可以狠狠的点击这里:meter元素与密码强度实现demo

接下来一步一步带大家了解具体的实现过程,先从了解 <meter> 元素开始。

二、meter 元素基本特性与效果

单词 meter 直译意思是“计量器”,“计量表”,因此,在 Web 中,任何与丈量相关,同时需要分阶段提示的场景都非常使用使用 <meter> 元素。

例如汽车油箱剩余油量,降雨量,风度,温度,游戏中人物角色的血量等。

先看下面一段代码:

生命值:<meter min="0" max="100" low="30" high="60" optimum="80" value="20"></meter>
生命值:<meter min="0" max="100" low="30" high="60" optimum="80" value="50"></meter>
生命值:<meter min="0" max="100" low="30" high="60" optimum="80" value="70"></meter>

在现代浏览器下就会有如下图所示的 UI 效果。

meter基本效果

下面则是实时渲染效果,大家可以看下自己当前访问设备中的效果:

生命值:

生命值:

生命值:

其中出现了 6 个 HTML 属性,正好涵盖了 <meter> 元素所有常用属性。

属性简介

  • minmax 属性表示数值范围,默认是 0-1,也就是如果 min 属性如果不设置,则认为是 0,max 属性不设置则认为是 1。
  • value 表示当前的值,默认是 0。
  • lowhigh 是比较特殊的 HTML 属性,目前仅出现在 <meter> 元素上,表示警戒值,其中 low 表示过低警戒值,high 表示过高警戒值。

    但是,大家务必注意,过低还是过高是否需要警戒还需要一个属性进行判定,那就是 optimum 属性。

  • optimum 属性表示最佳值,用来决定过低和过高值属于正常还是异常,这个属性很重要,也是 <meter> 元素学习的难点。

optimum 属性

optimum 属性的作用表现描述如下:

  • 如果 optimum 属性值在 lowhigh 之间,则说明 lowhigh 都是警戒值,只有在这个区间范围的值才是正常的,因此,最终的色值状态只会有两个,即橙色警戒和绿色正常。

    代码示意:

    生命值:<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>

    效果如下图所示,最多只会出现两种颜色。

    两种色值

  • 如果 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>

    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>

    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 效果了。

然而,事情并没有预想的那么简单。

  1. 密码强度如何确定?
  2. 表示 3 个颜色的伪元素最多只会出现 1 个,而我们的目标效果却是 3 色同时显示,另外,默认的色值是连续的,而需要的效果是分段的,该怎么实现呢?
  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 好了。

五、最终样式实现的细节

1. 背景条的样式

首先,我们确定个尺寸,假设 长度是 120px,高度是 12px:

::-webkit-meter-bar {
    width: 120px;
    height: 12px;
}

浏览器默认的边框效果是不需要的,然后为了兼容性,需要统一背景色,于是有:

::-webkit-meter-bar {
    width: 120px; height: 12px;
    border: 0;
    background: #eee;
}

Firefox 浏览器可以使用 ::-moz-meter-bar 伪元素设置。

2. 色带的实现

浏览器默认的状态都是纯色,从左纯到右,想要变成一个一个的色带,不少人的想法会是叠加,也就是几个 <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 渐变重新实现之后,就是 红-橙-绿 三段,很好地模拟了三态强度效果。

最终的效果可以参见下面的 GIF 录屏。

三色模拟效果

3. 中间镂空分隔

上面的 GIF 效果,各个色块是紧密相连的,但是最终需要的效果是彼此之间有间隙,这个该如何实现呢?

方法其实挺多的,我这里选择使用遮罩实现,在色块的容器元素上绘制一个镂空渐变作为遮罩图像:

::-webkit-meter-bar {
    -webkit-mask: linear-gradient(to right, 
      red 39px, 
      transparent 0 41px, 
      orange 0 79px, 
      transparent 0 81px, 
      green 0
    );
}

此时,效果就会是这样:

带间隙的强度效果

4. 文字左中右对齐实现

接下来就是“弱中强”三个文字的效果实现了,如下图:
目标密码强度效果

有人会想到使用一个 <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 行显示,从而让第一行的 “弱中强”三个字两端对齐。

效果如下所示:

文字对齐效果

我猜,有人会说,不对啊,“弱”和“强”两个字需要在色块的中间啊,不是边上。

对,边上是不对的,所以,需要修改下 lefttop 位置微调下(中文字符占据宽度是 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;
}

效果为:

对齐效果

接下来,就是文字变色的实现了。

5. 不同文字不同颜色实现

同一段文字显示不同颜色的技术其实很多很多年前(10年前)就介绍过了,详见:“CSS3下的渐变文字效果实现”,在《CSS新世界》这本书中也有介绍。

这里使用 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;
}

再加上一些尺寸和位置的细节调整,于是就有了最终的效果:

最终渐变文字效果

6. Safari 浏览器

好,Chrome 浏览器搞定了,看看 Safari 浏览器下的效果,结果……呃……:

Safari下效果

本以为是safari浏览器不支持,后来研究了一下,发现原来只需要设置外面的meter 元素边框尺寸为0或none就可以触发伪元素重定义了,也就是:

meter {
    border: 0;
}

所有的效果就会出现,如下所示:

密码效果Safari

7. 使用 CSS 变量优化

最后把上面所有的实现整合一下,同时使用css变量优化一下渐变以及尺寸。就会得到如下所示的终极代码:

meter {
     --size: 60px;
     --gradient: linear-gradient(to right, red calc(var(--size) - 1px), transparent 0 calc(var(--size) + 1px), orange 0 calc(var(--size) * 2 - 1px), transparent 0 calc(var(--size) * 2 + 1px), green 0);
    width: calc(3 * var(--size));
    border: 0; /* Safari */
    position: relative;
}
meter::after {
    content: '弱 中 强 aaaaaaaaaaaaaaaaaaaaaa';
    position: absolute;
    font-size: 14px;
    line-height: 20px;
    height: 20px;
    overflow: hidden;
    left: calc(var(--size) / 2 - .5em);
    right: calc(var(--size) / 2 - .5em);
    text-align: justify;
    -webkit-text-fill-color: transparent;
    background: var(--gradient) calc(.5em - var(--size) / 2) / calc(3 * var(--size));
    -webkit-background-clip: text;
}
::-webkit-meter-bar {
    height: 12px;
    width: calc(3 * var(--size));
    border: 0;
    background: #eee;
    -webkit-mask: var(--gradient);
    mask: var(--gradient);
}
::-webkit-meter-even-less-good-value {
    background: red;
}
::-webkit-meter-suboptimum-value {
    background: linear-gradient(to right, red var(--size), orange 0);
}
::-webkit-meter-optimum-value {
    background: linear-gradient(to right, red var(--size), orange 0 calc(2 * var(--size)), green 0);
}

如果想要改变强度测量条元素的宽度,只需要修改CSS自定义属性 --size 的值就可以了。

眼见为实,效果体验

您可以狠狠地点击这里:meter元素自定义效果纯享版demo

进行效果体验。

至此,所有相关实现的介绍都结束了。

六、总结

当然,正如一开始所提到的,<meter> 元素不仅可以用来表示密码强度。在 Web 中,所有与丈量、测量相关的场景,都可以考虑使用这一个 HTML 元素。

有人可能会纠结 IE 浏览器的问题。

毕竟 <meter> 元素的兼容性是这样子的。

meter 元素的兼容性

一种方法是使用polyfill,我这里找了个项目:https://github.com/fisker/meter-polyfill

这个项目的作者可能是国人,因为头像看起来非常熟悉,不过这个 polyfill 项目我自己没试过,所以质量如何并不清楚。

从我自己的角度讲,如果我是遇到这样的场景,我会说服产品直接放弃 IE浏览器,就不用搞什么所谓的密码强度了,本来这种密码强度就是锦上添花的功能,没有的话,也不影响功能。

况且现在 IE 浏览器占比其实非常低了,3%都不到,我不知道其他公司产品怎样的,以及我们开发产品总是要面向未来的,你不能说我当下还有一些 IE 浏览器用户,你就放弃了未来,想想看,现在项目中还有兼容 IE8 浏览器的代码,是不是想吐。

当然,如果产品是个直溜子说服不通,以为工程师就是想偷懒。

那我觉得专门给IE做一个特殊的处理,其实也不需要多大的成本,回头哪天 IE 嗝屁了直接移除就好。

好,巴拉巴拉,断断续续,没想到讲了这么多,这篇文章篇幅又铺出来了,所以就不再啰嗦了。

行文不易,欢迎分享,感谢转发!

(本篇完)

分享到:


发表评论(目前21 条评论)

  1. 飞飞说道:

    我们公司产品用户还有使用XP 和win5的,😫

  2. iehyou说道:

    我就吐槽一下现在互联网企业,天天要求复杂的密码,那么多网站,我那里记得了这么多,你的业务对于我来不重要。输入一个密码烦死了。 实在不记得了 找回还不能设置之前设置过得密码。又要想一个新密码 哎

  3. 小苏打说道:

    大佬,optimum 属性 第二点的代码value都是20,是不是没改啊

  4. 码农说道:

    –size大于105px时,aaaaa会露出来,文字颜色也不对。。。
    可以看看我改了之后的
    https://codepen.io/pandamo-Code/pen/pormWZy

    主要修改 meter::after
    去掉:
    left: calc(var(–size) / 2 – .5em);
    right: calc(var(–size) / 2 – .5em);
    text-align: justify;

    修改:
    background: var(–gradient) calc(.5em – var(–size) / 2) / calc(3 * var(–size));

    background: var(–gradient);

    增加:
    width: calc(3 * var(–size));
    text-align-last: justify;
    padding: 0 calc(0.4 * var(–size));
    box-sizing: border-box;

  5. Dash说道:

    大佬们,我想问下这种::-webkit的属性是具体在哪里可以查得到啊

  6. qianyin925说道:

    optimum 属性介绍, 第二个演示代码好像是错的, value 值应该没改

  7. 依韵说道:

    给大佬点赞,真是能玩出花来,太棒了

  8. 来不及说道:

    我看不懂,但深感震撼!🐶

  9. Chao说道:

    最后的“行为不易”应该是“行文不易”嘛?

  10. Mmm说道:

    text-align-end: justify;

    行末两段对齐。

  11. 代码如诗如画说道:

    大为震撼

  12. Johnson说道:

    第一次见,学到了

  13. thetbw说道:

    最后的那个demo在firefox中打开样式还是有问题,没有分隔条,不显示下面的字。

  14. DeathGhost说道:

    路过,一赞!