当前位置: 首页 > 科技观察

从零开始开发一个轻量级的滑动验证码插件(深度回顾)

时间:2023-03-13 08:10:23 科技观察

之前一直在分享低代码和可视化的文章,其中涉及到很多有趣的知识点和设计思路。今天继续给大家分享一个很有趣也很实用的前端实战项目——从零开始实现一个基于react+canvas的滑动验证码,并发布到npm上供大家使用。当然,如果你更喜欢vue开发方式,也不用担心,本文的设计思路和思路是通用的。如果你想学习如何打包vue组件并发布到npm上,也可以参考我之前的文章:从零到一教你开发基于vue的组件库。从这个实际项目中,我们可以学到以下知识点:前端组件设计的基本思想和技巧Canvas基础知识和ReactHooks的使用滑动验证码的基础知识和使用基本设计原则如何封装一个可扩展的滑动验证码component如何使用dumi构建组件文档如何发布你的第一个npm组件包如果你对以上任何一个知识点感兴趣,相信本文会对你有所启发。效果演示滑动验证组件的基本用法和技术实现上图是滑动验证组件实现的效果演示。当然还有很多配置项可以选择,支持更多的自定义场景。下面我先介绍一下如何安装和使用这个验证码插件,让大家有个直观的体验,然后再详细介绍一下滑动验证码的实现思路。如果有一定的技术基础,也可以直接跳转到技术实现部分。基本使用因为我已经在npm上发布了react-slider-vertify组件,大家可以按如下方式安装使用:1.安装#oryarnadd@alex_xu/react-slider-vertifynpmi@alex_xu/react-slider-vertify-S2。使用importReactfrom'react';import{Vertify}from'@alex_xu/react-slider-vertify';exportdefault()=>{returnalert('成功')}onFail={()=>alert('失败')}onRefresh={()=>alert('刷新')}/>};通过以上两步我们就可以轻松的使用这个滑动验证码组件了,是不是很简单呢?当然我也暴露了很多可配置的属性,让大家对组件有更好的控制。参考如下:技术实现在做这个项目之前,我也学习了一些滑动验证码的知识和现有的技术方案,收获颇丰。下面我将用我的组件设计思路来介绍一下如何使用react来实现和封装滑动验证码组件。如果大家有更好的想法和建议,也可以随时在评论区反馈给我。1.组件设计的思路和技巧每个人都有自己的组件设计方式和风格,但最终的目的是让组件设计得更优雅。在这里我大致列举了优雅组件的设计指标:可读性(统一清晰的代码格式、完整的注释、清晰的代码结构、正确使用编程范式)易用性(完整的代码功能、不同场景良好的兼容、业务逻辑的覆盖)可重用性(代码可以被其他业务模块复用)可维护性(代码易于维护和扩展,具有一定的向下/向上兼容性)高性能是我自己设计组件时考虑的指标,大家可以参考。此外,我们需要在设计组件之前明确需求。以滑动验证码组件为例。我们需要知道它的使用场景(登录注册、活动、论坛、短信等高风险业务场景的人机验证服务)和需求。(交互逻辑,如何验证,需要暴露哪些属性)。以上是我整理的一个通用的组件开发需求。在开发具体组件之前,如果遇到复杂的业务逻辑,我们也可以把每个实现步骤都列出来,然后一一实现,这样有助于理清思路。和更高效的发展。2.滑动验证码的基本实现原理介绍完组件设计思路和需求分析,我们来看看滑动验证码的实现原理。我们都知道设计验证码的主要目的是防止机器非法暴力入侵我们的应用。要解决的核心问题是确定谁在操作应用程序(人还是机器),因此通常的解决方案是随机识别。在上图中,我们可以看到,只有用户手动将滑块拖动到相应的镂空区域,才能验证成功。镂空区域的位置是随机的(随机性测试暂时在前端实现,比较安全的方式是通过后端返回位置和图片)。基于以上分析,我们可以画出一个基本的滑动验证码设计示意图:接下来,我们将把这个可扩展的滑动验证码组件封装在一起。3、封装一个可扩展的滑动验证码组件。按照我一贯的开发组件的风格,我会先根据需求写出组件的基础框架:importReact,{useRef,useState,useEffect,ReactNode}from'react';interfaceIVertifyProp{/***@descriptioncanvaswidth*@default320*/width:number,/***@descriptioncanvasheight*@default160*/height:number,/***@description滑块边长*@default42*/l:number,/***@description滑块半径*@default9*/r:number,/***@descriptionisvisible*@defaulttrue*/visible:boolean,/***@descriptionslidertext*@defaultrightSlidetofillthepuzzle*/text:string|ReactNode,/***@description刷新按钮图标为图标的url地址*@default-*/refreshIcon:string,/***@description用于获取随机图片的url地址*@defaulthttps://picsum.photos/${id}/${width}/${height},详见https://picsum.photos/,只需要实现一个类似的接口即可*/imgUrl:string,/***@description验证成功回调*@default():void=>{}*/onSuccess:VoidFunction,/***@description验证失败回调*@default():void=>{}*/onFail:VoidFunction,/***@description回调*@default():void=>{}*/onRefresh:VoidFunction}exportdefault({width=320,height=160,l=42,r=9,imgUrl,text,refreshIcon='http://yourimgsite/icon.png',visible=true,onSuccess,onFail,onRefresh}:IVertifyProp)=>{return

{textTip}Loading...}以上是我们组件的基本框架结构。从代码中,我们可以一目了然地发现组件属性。这些都是提前组织需求的好处,可以让我们写组件的时候思路更清晰。写完基本的css样式,我们看到的界面是这样的:接下来我们需要实现如下核心功能:镂空效果画布图片实现镂空图案画布实现滑块移动和验证逻辑实现以上描述可能比较抽象,我画个图来说明一下:因为组件实现完全使用了reacthooks,对hooks不熟悉的也可以参考我之前的文章:10分钟教你手写8个常用自定义hooks1.实现Canvas图片的镂空效果在开始编码之前,我们需要对canvas有一个基本的了解。建议不熟悉的朋友可以参考高效的canvas学习文档:MDN的Canvas。从上图可以看出,首先要解决的问题是如何用canvas绘制不规则的图形。这里我简单画了个草图:我们只需要使用canvas提供的pathapi来绘制上图中的路径,路径填充任意半个透明色即可。建议不熟悉的可以先了解一下下面的api:beginPath()开始绘制路径moveTo()将笔画移动到指定点arc()画弧线lineTo()画线stroke()strokefill()fillclip()cut路径实现方法如下:constdrawPath=(ctx:any,x:number,y:number,operation:'fill'|'clip')=>{ctx.beginPath()ctx.moveTo(x,y)ctx.arc(x+l/2,y-r+2,r,0.72*PI,2.26*PI)ctx.lineTo(x+l,y)ctx.arc(x+l+r-2,y+l/2,r,1.21*PI,2.78*PI)ctx.lineTo(x+l,y+l)ctx.lineTo(x,y+l)//逆时针为布尔值。为true时为逆时针,否则为顺时针ctx.arc(x+r-2,y+l/2,r+0.4,2.76*PI,1.24*PI,true)ctx.lineTo(x,y)ctx.lineWidth=2ctx.fillStyle='rgba(255,255,255,0.8)'ctx.strokeStyle='rgba(255,255,255,0.8)'ctx.stroke()ctx.globalCompositeOperation='destination-over'//判断填充还是切割Cutting,cutting主要用于生成patternslideroperation==='fill'?ctx.fill():ctx.clip()}这个实现方案也参考了yieldboss的原生js实现,这里需要加上一点是canvas的globalCompositeOperation属性,它的主要作用是设置如何将源(新)图像绘制到目标(现有)图像上。sourceimage=我们打算放在画布上的画targetimage=我们已经放在画布上的画w3c有一个图像的例子:这里设置这个属性的原因是为了让挖空的形状不受影响背景底图和叠加在背景底图之上。如下:接下来我们只需要在画布上绘制图片即可:constcanvasCtx=canvasRef.current.getContext('2d')//绘制空心形状drawPath(canvasCtx,50,50,'fill')//绘制图片canvasCtx.drawImage(img,0,0,width,height)当然,至于如何生成随机图片和随机位置,实现方法也很简单。对于前端实现,可以使用Math.random。2.实现镂空图案在画布上实现镂空形状,所以镂空图案是相似的。我们只需要使用clip()方法将图片裁剪成形状蒙版,并将镂空图案放置在画布的左侧即可。代码如下:constblockCtx=blockRef.current.getContext('2d')drawPath(blockCtx,50,50,'clip')blockCtx.drawImage(img,0,0,width,height)//提取图案滑块并放在Leftmostconsty1=50-r*2-1constImageData=blockCtx.getImageData(xRef.current-3,y1,L,L)//调整滑块画布宽度blockRef.current.width=LblockCtx.putImageData(ImageData,0,y1)在上面的代码中,我们使用了getImageData和putImageData。这两个API主要用于获取canvas场景的像素数据,以及将像素数据写入场景。实现后的效果如下:3.实现滑块移动和验证逻辑实现滑块移动的方案比较简单,我们只需要用到鼠标的event事件:onMouseDownonMouseMoveonMouseUp以上是一个简单的示意图,具体实现代码如下:consthandleDragMove=(e)=>{if(!isMouseDownRef.current)returnfalse.preventDefault()//为了支持移动端,可以使用e.touches[0]consteventX=e.clientX||e.touches[0]。clientXconsteventY=e.clientY||e.touches[0].clientYconstmoveX=eventX-originXRef.currentconstmoveY=eventY-originYRef.currentif(moveX<0||moveX+36>=width)返回falsesetSliderLeft(moveX)constblockLeft=(width-l-2r)/(width-l)*moveXblockRef.current.style.left=blockLeft+'px'}当然我们还需要监听拖拽停止后的事件判断是否验证成功,嵌入成功和失败回调。代码如下:constandleDragEnd=(e)=>{if(!isMouseDownRef.current)returnfalseisMouseDownRef.current=falseconsteventX=e.clientX||e.changedTouches[0].clientXif(eventX===originXRef.current)returnfalsesetSliderClass('sliderContainer')const{flag,result}=verify()if(flag){if(result){setSliderClass('sliderContainersliderContainer_success')//自定义成功后的回调函数typeofonSuccess==='function'&&onSuccess()}else{//验证失败,刷新并重置setSliderClass('sliderContainersliderContainer_fail')setTextTip('Pleasetryagain')reset()}}else{setSliderClass('sliderContainersliderContainer_fail')//自定义失败后回调函数typeofonFail==='function'&&onFail()setTimeout(reset.bind(this),1000)}}执行后效果如下:当然还有一些细节需要优化。这里是github上的完整代码。大家可以参考学习。如果你想为这个组件做贡献,你可以随时提出问题。4.如何使用dumi构建组件文档为了让组件更好的被别人理解和使用,我们可以构建组件文档。作为一个热爱开源的前端码农,编写组件文档也是一个很好的开发习惯。接下来我们还要编写react-slider-vertify的组件文档。这里我使用dumi构建组件文档。当然,您也可以使用其他解决方案(例如故事书)。看一下搭建后的效果:dumi的搭建组件文档很简单,接下来介绍一下安装和使用方法。1、安装$npx@umijs/create-dumi-lib#初始化一个文档模式的组件库开发脚手架#或者$yarncreate@umijs/dumi-lib$npx@umijs/create-dumi-lib--site#初始化一个站点模型组件库开发脚手架#or$yarncreate@umijs/dumi-lib--site2.本地运行npmrundev#oryarndev3.编写文档Dumi约定俗成的定义了文档编写的位置和方法,其官网也有具体的介绍,这里dumi搭建的组件目录结构简单图:我们可以在docs下写组件库文档的首页和引导页的说明,在文件夹下用index.md写组件本身的使用文档单个组件。当然整个过程很简单,这里有一个文档的例子:这样dumi可以帮我们自动渲染一个组件使用文档。如果想了解更多构建组件文档,也可以到dumi官网学习。5.发布你的第一个npm组件包最后一个问题是组件发布。之前有很多朋友问我如何把自己的组件发布到npm上供更多人使用。这个知网上有很多资料需要学习,所以今天就以滑动验证码@alex_xu/react-slider-vertify为例。让我给你做一个简单的介绍。1、拥有npm账号并登录如果之前没有npm账号,可以在npm官网注册一个,然后用我们熟悉的IDE终端登录一次:npmlogin按照提示输入用户名和密码,我们可以通过命令行发布组件包添加:npmpublish--accesspublic命令后面之所以带public参数是为了避免权限问题导致组件包发布失败.为了省事,我们也可以在package.json中配置发布命令,组件打包后自动发布:{"scripts":{"start":"dumidev","re??lease":"npmrunbuild&&npmpublish--accesspublic",}}这样我们就可以很方便的将组件发布到npm中供其他人使用!之前我也开源过很多组件库。如果对组件打包细节和构建过程有任何疑问,也可以参考我之前的开源项目解决方案。发布到npm后的效果:本文转载自微信公众号《趣谈前端》