【前端】制作一个拼图验证码

2,366 阅读7分钟

大家好,我是 Steven。

这一期我们会做一个拖拉形式的拼图验证码,是近年很常见的一种验证码方式,有别于传统的猜文字的那种:

Intro.gif

那我们就开始吧。

这个教程的视频版本在 www.bilibili.com/video/BV1NM… ,欢迎三连关注!

基础架构

打开 CodePen 编辑器,在 HTML 的部份加入一个 <div>idcaptcha

来到 CSS 的部份,加入 body 选择器,使用 Flexbox 的方式将内容上下左右居中。加入 #captcha 选择器,display 设定为 block,宽度设定为 400px,高度设定为 260px,由于稍后会用到这个宽度和高度进行运算,所以将它们分拆,写成 CSS 变量。

01.jpg

加入少许圆角,以及设置背景图片。背景图片就是拼图的图片,在 Unsplash 的网站上,找到这张挺适合的图片:

02.jpg

然后复制它的连结,套用到 background-image 上。为了让它填满这个容器,background-size 设定为 cover‌,background-position 设定为中间 center,然后 position 设定为 relative,再加入一点阴影,就可以了。

03.jpg

两块拼图

接下来要制作两块拼图,一块在外面,一块在里面。

里面的那块是拼图的缺口的提示,既然刚好是两块,我就直接用 Pseudo Element (伪元素)制作。

加入 #captcha::before#captcha::after 选择器,position 设定为 absolute,要将伪元素显示出来,必须要设置 content 属性,设定为空白字串。display 设定为 block,接下来的几项设定,都是根据父元素去设定,包括宽度、高度、背景图片、background-size 以及 background-position

04.jpg

因为背景图片是一样的,所以看不出有任何分别,暂时将背景图片的设定移除,将背景颜色设定为红色,就可以看到伪元素的大小和位置了。

05.jpg

接下来就要设置拼图方块的大小,回到 #captcha 那里,设定两个 CSS 变量,分别是 --puzzle-width--puzzle-height,都设定为 80px

然后通过 clip-path 属性的 inset(),设定按上右下左向内缩小的方式制作遮罩,这里先设定 -webkit 的版本。

第一个设定值是由上向内方向缩小,设定为 calc()var(--height) 减去 var(--puzzle-height) 再除以 2,会得出 90px

第二个设定值,是从右向内方向缩小,设定为一个拼图方块的大小就好了,var(--puzzle-width)

第三个设定值,是从下向内方向缩小,这个设定与第一个设定值相同,复制贴上一下。

第四个设定值,是从左向内方向缩小,设定为 calc()var(--width) 减去 var(--puzzle-width) 乘以 2,得出 230px

现在看到红色方块的位置,就是 80px 乘以 80px 的正方形。复制一下 -webkit 的设定,同时套用到 clip-path

06.jpg

然后将其中一个方块向左移出来,加入 #captcha::after 选择器,设定 transform: translatex(-300px)。将红色的方块改回背景图片,这个拼图方块就被移出来了,将它改为 calc(var(--width) * -1) 更加精确的将它对齐。

07.jpg

然后里面的那个方块怎样设置呢?加入 #captcha::before 选择器,设定背景颜色为 60% 透明度的黑色,然后设置 background-blend-modemultiply,就可以将半透明的黑色,与背景图片混合到一起。

08.jpg

拉动条

这个部份制作完成了,接下来制作下方的拉动条,在 HTML 的 #captcha 内加入一个 <div>id 名为 handle,再在里面加入一个 span,用于制作里面的按钮。不用伪类元素制作按钮的原因是,在 JavaScript 中不能直接监听伪类元素的事件。

09.jpg

在 CSS 的部份加入 #handle 选择器,宽度设定为 calc()var(--width),加上 var(--puzzle-width) 乘以 2,高度设定为 30px,圆角设定为 15px

背景颜色设定为浅灰色,position 设定为 absolute,移到下方,bottom 设定为 -50px,再向左移动,left 设定为 calc()var(--puzzle-width) * 2 * -1,加入一点向内的阴影,制作一点立体感,再加一个浅灰色的框线。由于加了框线,高度不同了,所以将圆角更改为 18px

10.jpg

然后处理拖拉的按钮,加入 #handle span 选择器,display 设定为 block,宽度设定为与拼图一样。高度和圆角的设定都继承自父元素,背景颜色设定为白色,再分别加入向内和向外的阴影,造出立体的效果。

position 设定为 absolute,以及改一改游标的样式,设定为 move

11.jpg

然后要处理它水平拖拉的位移,先加入一个 CSS 变量,名为 --moved,赋值为 0px;或者先赋值为 200px,这样方便查看效果:

12.jpg

通过 transform: translatex() 移动它。而这里需要有个最小和最大值,不然就会超出这条杆。加入 clamp() 函式,第一个是最小值,设定为 0px,第二个是 --moved 的值,而第三个是最大值,设定为宽度加拼图的宽度。

13.jpg

回到 --moved 那里,试试改为极端的数字,例如 1000px 就会移到最右,而 -1000px 就会移到最左。先将它暂时改为 400px,方便我们处理拼图的位移。

去到 #captcha::after 的选择器,改为 clamp(),最小值设定为宽度乘以 -1,移动的值设定为宽度乘以 -1,再加 --moved。而最大值,就等于拼图的宽度,现在试一试更改 --moved 的值,看看效果,大致上就是这样了。

14.jpg

好了,现在我们知道只要调整 --moved 的值,就可以同步移动拖拉按钮以及拼图。所以下一步只需要加入 JavaScript 的逻辑,更改 --moved 的值就可以了。

JavaScript 逻辑的部分

去到 JavaScript 的部份,先定义几个常量,获取会用到的选择器,分别有 captchahandle 以及 button

然后加入三个事件监听器,分别是:

  • mousedown,点击下去的时候;
  • mousemove 移动的时候;
  • mouseup 放手的时候。

这里也要定义一个 flag,叫做 shouldMove,用来判断是否在点击的状态下。

15.jpg

然后在 mousedown 的时候,将它设定为 true

16.jpg

去到 mousemove 里面,如果 shouldMovetrue 的话,定义常量 offsetLeft,获取拉动杆与画面左边的位移。再定义常量 buttonWidth,获取按钮的宽度。以上两个常量配合游标的位置,就可以计算出正确的 --moved 的结果,通过 setProperty(),更改 #captcha--moved 的值,即是 CSS 中的这个变量。

设定为 e.clientX,即是游标的 X 位置,减去 offsetLeft,再减去 buttonWidth2,游标的 X 位置,减移动杆与左边的距离,再减按钮宽度除 2。除 2 的原因是中心点设定到按钮的中间位置。

17.jpg

然后在 mouseup 那里,将 shouldMove 设定为 false

18.jpg

移动的效果实现到了,但发现有一个问题,就是在拖拉的过程中,如果游标不是在按钮上面,就会拖不动,原因是已经离开了 button 本身,事件监听器就监听不到了。所以将 mousemovemouseup 的监听,从 button 改为 window,就可以了。

19.jpg

最后,处理成功拼图的逻辑。

判断当 shouldMovetrue 的时候,将 shouldMove 等于 false 移到里面。然后定义常量 finalOffset 获取拖动的距离,将 e.clientX 减去拖拉杆向左的位移,通过 console.log() 输出一下。

20.jpg

这种验证码通常会允许少许的误差,所以我们就将 430450 作为可接受的范围,如果不在这个范围的话,将 --moved 设定为 0px,让按钮回到原来的位置;如果成功的话,就加一个 class 名为 passed#captcha 上,我们再到 CSS 那边处理样式的改变。

21.jpg

回到 CSS 的部份,当 #captchapassed 的时候,将 ::before::after 和里面的 #handle 的透明度设定为 0

22.jpg

然后我想在拼图失败的时候,加入一个回弹的效果。去到 #handle span 里面,加入 transition 的设定:

虽然可以做到回弹的效果,不过这个 transition 的设定,并不想套用在拖动的过程中,所以我们可以通过 #captcha:active,即是点下去的状态,设定 #handle spantransitionnone

23.jpg

同样的设定,套用到 #captcha::after 选择器里面。

24.jpg

我们来看看这个案例的完成效果

Intro.gif

以上,就是这一集要介绍的全部内容。


这个案例的源代码在 codepen.io/stevenlei/p…

你的支持是我的动力,请关注 CodingStartup 起码课,我们一起加油!