3

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

 2 years ago
source link: https://www.zhangxinxu.com/wordpress/2021/11/html-meter-password/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10188 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

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

密码强度截图

这种效果实现最好的方法一定是使用 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="20"></meter>
    生命值:<meter max="100" low="30" high="60" optimum="20" value="20"></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 嗝屁了直接移除就好。

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

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

1f913.svg

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK