从零开发一款轻量级滑动验证码插件

简介: 之前一直在分享 低代码 和 可视化 的文章,其中涉及到很多有意思的知识点和设计思想,今天继续和大家分享一款非常有趣且实用的前端实战项目——从零基于 react + canvas 实现一个滑动验证码,并将其发布到 npm 上供他人使用。当然如果大家更喜欢 vue 的开发方式,也不用担心,文中的设计思想和思路都是通用的,如果大家想学习如何封装 vue 组件并发布到 npm 上,也可以参考我之前的文章: 从零到一教你基于vue开发一个组件库。

网络异常,图片无法展示
|



之前一直在分享 低代码可视化 的文章,其中涉及到很多有意思的知识点和设计思想,今天继续和大家分享一款非常有趣且实用的前端实战项目——从零基于 react + canvas 实现一个滑动验证码,并将其发布到 npm 上供他人使用。当然如果大家更喜欢 vue 的开发方式,也不用担心,文中的设计思想和思路都是通用的,如果大家想学习如何封装 vue 组件并发布到 npm 上,也可以参考我之前的文章: 从零到一教你基于vue开发一个组件库


从这个实战项目中我们可以学到如下知识点:


  • 前端组件设计的基本思路和技巧
  • canvas 基本知识和使用
  • react hooks 基本知识和使用
  • 滑动验证码基本设计原理
  • 如何封装一款可扩展的滑动验证码组件
  • 如何使用 dumi 搭建组件文档
  • 如何发布自己第一个npm组件包


如果你对以上任意知识点感兴趣,相信这篇文章都会给你带来启发。


效果演示



网络异常,图片无法展示
|


滑动验证组件基本使用和技术实现



上图是实现的滑动验证组件的一个效果演示,当然还有很多配置项可以选择,以便支持更多 定制化 的场景。接下来我先介绍一下如何安装和使用这款验证码插件,让大家有一个直观的体验,然后我会详细介绍一下滑动验证码的实现思路,如果大家有一定的技术基础,也可以直接跳到技术实现部分。


基本使用



因为 react-slider-vertify 这款组件我已经发布到 npm 上了,所以大家可以按照如下方式安装和使用:


  1. 安装


# 或者 yarn add @alex_xu/react-slider-vertify
npm i @alex_xu/react-slider-vertify -S


  1. 使用


import React from 'react';
import { Vertify } from '@alex_xu/react-slider-vertify';
export default () => {
    return <Vertify 
            width={320}
            height={160}
            onSuccess={() => alert('success')} 
            onFail={() => alert('fail')} 
            onRefresh={() => alert('refresh')} 
        />
};


通过以上两步我们就可以轻松使用这款滑动验证码组件了,是不是很简单?

网络异常,图片无法展示
|


当然我也暴露了很多可配置的属性,让大家对组件有更好的控制。参考如下:


网络异常,图片无法展示
|


技术实现


在做这个项目之前我也研究了一些滑动验证码的知识以及已有的技术方案,收获很多。接下来我会以我的组件设计思路来和大家介绍如何用 react 来实现和封装滑动验证码组件,如果大家有更好的想法和建议, 也可以在评论区随时和我反馈。


1.组件设计的思路和技巧


每个人都有自己设计组件的方式和风格,但最终目的都是更 优雅 的设计组件。这里我大致列举一下 优雅 组件的设计指标:


  • 可读性(代码格式统一清晰,注释完整,代码结构层次分明,编程范式使用得当)
  • 可用性(代码功能完整,在不同场景都能很好兼容,业务逻辑覆盖率)
  • 复用性(代码可以很好的被其他业务模块复用)
  • 可维护性(代码易于维护和扩展,并有一定的向下/向上兼容性)
  • 高性能


以上是我自己设计组件的考量指标,大家可以参考一下。


另外设计组件之前我们还需要明确需求,就拿滑动验证码组件举例,我们需要先知道它的使用场景(用于登录注册、活动、论坛、短信等高风险业务场景的人机验证服务)和需求(交互逻辑,以什么样的方式验证,需要暴露哪些属性)。


网络异常,图片无法展示
|


以上就是我梳理的一个大致的组件开发需求,在开发具体组件之前,如果遇到复杂的业务逻辑,我们还可以将每一个实现步骤列举出来,然后一一实现,这样有助于整理我们的思路和更高效的开发。


2.滑动验证码基本实现原理


在介绍完组件设计思路和需求分析之后,我们来看看滑动验证码的实现原理。


网络异常,图片无法展示
|


我们都知道设计验证码的主要目的是为了防止机器非法暴力地入侵我们的应用,其中核心要解决的问题就是判断应用是谁在操作( or 机器),所以通常的解决方案就是随机识别


上图我们可以看到只有用户手动将滑块拖拽到对应的镂空区域,才算验证成功,镂空区域的位置是随机的(随机性测试这里暂时以前端的方式来实现,更安全的做法是通过后端来返回位置和图片)。


基于以上分析我们就可以得出一个基本的滑动验证码设计原理图:


网络异常,图片无法展示
|


接下来我们就一起封装这款可扩展的滑动验证码组件。


3.封装一款可扩展的滑动验证码组件


按照我开发组件一贯的风格,我会先基于需求来编写组件的基本框架:


import React, { useRef, useState, useEffect, ReactNode } from 'react';
interface IVertifyProp {
    /**
     * @description   canvas宽度  
     * @default       320
     */
    width:number, 
    /**
     * @description   canvas高度  
     * @default       160
     */
    height:number, 
    /**
     * @description   滑块边长  
     * @default       42
     */
     l:number,
     /**
     * @description   滑块半径 
     * @default       9
     */
      r:number,
     /**
     * @description   是否可见
     * @default       true
     */
      visible:boolean,
     /**
     * @description   滑块文本
     * @default       向右滑动填充拼图
     */
      text:string | ReactNode,
      /**
     * @description   刷新按钮icon, 为icon的url地址
     * @default       -
     */
       refreshIcon:string,
     /**
     * @description   用于获取随机图片的url地址
     * @default       https://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
}
export default ({ 
    width = 320,
    height = 160,
    l = 42,
    r = 9,
    imgUrl,
    text,
    refreshIcon = 'http://yourimgsite/icon.png',
    visible = true,
    onSuccess,
    onFail,
    onRefresh
 }: IVertifyProp) => {
     return <div className="vertifyWrap">
        <div className="canvasArea">
            <canvas width={width} height={height}></canvas>
            <canvas className="block" width={width} height={height}></canvas>
        </div>
        <div className={sliderClass}>
            <div className="sliderMask">
                <div className="slider">
                    <div className="sliderIcon">&rarr;</div>
                </div>
            </div>
            <div className="sliderText">{ textTip }</div>
        </div>
        <div className="refreshIcon" onClick={handleRefresh}></div>
        <div className="loadingContainer">
            <div className="loadingIcon"></div>
            <span>加载中...</span>
        </div>
    </div>
 }


以上就是我们组件的基本框架结构。从代码中可以发现组件属性一目了然,这都是提前做好需求整理带来的好处,它可以让我们在编写组件时思路更清晰。在编写好基本的 css 样式之后我们看到的界面是这样的:


网络异常,图片无法展示
|


接下来我们需要实现以下几个核心功能:


  • 镂空效果的 canvas 图片实现
  • 镂空图案 canvas 实现
  • 滑块移动和验证逻辑实现


上面的描述可能比较抽象,我画张图示意一下:


网络异常,图片无法展示
|


因为组件实现完全采用的 react hooks ,如果大家对 hooks 不熟悉也可以参考我之前的文章:



1.实现镂空效果的 canvas 图片


网络异常,图片无法展示
|


在开始 coding 之前我们需要对 canvas 有个基本的了解,建议不熟悉的朋友可以参考高效 canvas 学习文档: Canvas of MDN


由上图可知首先要解决的问题就是如何用 canvas 画不规则的图形,这里我简单的画个草图:


网络异常,图片无法展示
|


我们只需要使用 canvas 提供的 路径api 画出上图的路径,并将路径填充为任意半透明的颜色即可。建议大家不熟悉的可以先了解如下 api :


  • beginPath() 开始路径绘制
  • moveTo() 移动笔触到指定点
  • arc() 绘制弧形
  • lineTo() 画线
  • stroke() 描边
  • fill() 填充
  • clip() 裁切路径


实现方法如下:


const drawPath  = (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)
    // anticlockwise为一个布尔值。为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 = 2
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'
    ctx.stroke()
    ctx.globalCompositeOperation = 'destination-over'
    // 判断是填充还是裁切, 裁切主要用于生成图案滑块
    operation === 'fill'? ctx.fill() : ctx.clip()
}


这块实现方案也是参考了 yield 大佬的原生 js 实现,这里需要补充的一点是 canvasglobalCompositeOperation 属性,它的主要目的是设置如何将一个源(新的)图像绘制到目标(已有)的图像上。


  • 源图像 = 我们打算放置到画布上的绘图
  • 目标图像 = 我们已经放置在画布上的绘图


w3c上有个形象的例子:


网络异常,图片无法展示
|


这里之所以设置该属性是为了让镂空的形状不受背景底图的影响并覆盖在背景底图的上方。如下:


网络异常,图片无法展示
|


接下来我们只需要将图片绘制到画布上即可:


const canvasCtx = canvasRef.current.getContext('2d')
// 绘制镂空形状
drawPath(canvasCtx, 50, 50, 'fill')
// 画入图片
canvasCtx.drawImage(img, 0, 0, width, height)



当然至于如何生成随机图片和随机位置,实现方式也很简单,前端实现的话采用 Math.random 即可。


2.实现镂空图案 canvas


上面实现了镂空形状,那么镂空图案也类似,我们只需要使用 clip() 方法将图片裁切到形状遮罩里,并将镂空图案置于画布左边即可。代码如下:


const blockCtx = blockRef.current.getContext('2d')
drawPath(blockCtx, 50, 50, 'clip')
blockCtx.drawImage(img, 0, 0, width, height)
// 提取图案滑块并放到最左边
const y1 = 50 - r * 2 - 1
const ImageData = blockCtx.getImageData(xRef.current - 3, y1, L, L)
// 调整滑块画布宽度
blockRef.current.width = L
blockCtx.putImageData(ImageData, 0, y1)


上面的代码我们用到了 getImageDataputImageData,这两个 api 主要用来获取 canvas 画布场景像素数据和对场景进行像素数据的写入。实现后 的效果如下:


网络异常,图片无法展示
|


3.实现滑块移动和验证逻辑


实现滑块移动的方案也比较简单,我们只需要利用鼠标的 event 事件即可:

  • onMouseDown
  • onMouseMove
  • onMouseUp


网络异常,图片无法展示
|


以上是一个简单的示意图,具体实现代码如下:


const handleDragMove = (e) => {
    if (!isMouseDownRef.current) return false
    e.preventDefault()
    // 为了支持移动端, 可以使用e.touches[0]
    const eventX = e.clientX || e.touches[0].clientX
    const eventY = e.clientY || e.touches[0].clientY
    const moveX = eventX - originXRef.current
    const moveY = eventY - originYRef.current
    if (moveX < 0 || moveX + 36 >= width) return false
    setSliderLeft(moveX)
    const blockLeft = (width - l - 2r) / (width - l) * moveX
    blockRef.current.style.left = blockLeft + 'px'
}


当然我们还需要对拖拽停止后的事件做监听,来判断是否验证成功,并埋入成功和失败的回调。代码如下:


const handleDragEnd = (e) => {
    if (!isMouseDownRef.current) return false
    isMouseDownRef.current = false
    const eventX = e.clientX || e.changedTouches[0].clientX
    if (eventX === originXRef.current) return false
    setSliderClass('sliderContainer')
    const { flag, result } = verify()
    if (flag) {
      if (result) {
        setSliderClass('sliderContainer sliderContainer_success')
        // 成功后的自定义回调函数
        typeof onSuccess === 'function' && onSuccess()
      } else {
        // 验证失败, 刷新重置
        setSliderClass('sliderContainer sliderContainer_fail')
        setTextTip('请再试一次') 
        reset()
      }
    } else {
      setSliderClass('sliderContainer sliderContainer_fail')
      // 失败后的自定义回调函数
      typeof onFail === 'function' && onFail()
      setTimeout(reset.bind(this), 1000)
    }
}


实现后的效果如下:


网络异常,图片无法展示
|


当然还有一些细节需要优化处理,这里在 github 上有完整的代码,大家可以参考学习一下,如果大家想对该组件参与贡献,也可以随时提 issue


4.如何使用 dumi 搭建组件文档


为了让组件能被其他人更好的理解和使用,我们可以搭建组件文档。作为一名热爱开源的前端 coder,编写组件文档也是个很好的开发习惯。接下来我们也为 react-slider-vertify 编写一下组件文档,这里我使用 dumi 来搭建组件文档,当然大家也可以用其他方案(比如storybook)。我们先看一下搭建后的效果:


网络异常,图片无法展示
|



网络异常,图片无法展示
|


dumi 搭建组件文档非常简单,接下来和大家介绍一下安装使用方式


安装


$ npx @umijs/create-dumi-lib        # 初始化一个文档模式的组件库开发脚手架
# or
$ yarn create @umijs/dumi-lib
$ npx @umijs/create-dumi-lib --site # 初始化一个站点模式的组件库开发脚手架
# or
$ yarn create @umijs/dumi-lib --site


  1. 本地运行


npm run dev
# or
yarn dev
  1. 编写文档


dumi 约定式的定义了文档编写的位置和方式,其官网上也有具体的饭介绍,这里简单给大家上一个 dumi 搭建的组件目录结构图:


网络异常,图片无法展示
|


我们可以在 docs 下编写组件库文档首页和引导页的说明,在单个组件的文件夹下使用 index.md 来编写组件自身的使用文档,当然整个过程非常简单,我这里举一个文档的例子:


网络异常,图片无法展示
|


通过这种方式 dumi 就可以帮我们自动渲染一个组件使用文档。如果大家想学习更多组件文档搭建的内容,也可以在 dumi 官网学习。


5.发布自己第一个npm组件包


最后一个问题就是组件发布。之前很多朋友问我如何将自己的组件发布到 npm 上让更多人使用,这块的知识网上有很多资料可以学习,那今天就以滑动验证码

@alex_xu/react-slider-vertify 的例子,来和大家做一个简单的介绍。


  1. 拥有一个 npm 账号并登录


如果大家之前没有 npm 账号,可以在 npm 官网 注册一个,然后用我们熟悉的 IDE 终端登录一次:


npm login


跟着提示输入完用户名密码之后我们就能通过命令行发布组件包了:


npm publish --access public


之所以指令后面会加 public 参数,是为了避免权限问题导致组件包无法发布成功。我们为了省事也可以把发布命令配置到 package.json 中,在组件打包完成后自动发布:


{
    "scripts": {
        "start": "dumi dev",
        "release": "npm run build && npm publish --access public",
  }
}


这样我们就能将组件轻松发布到 npm 上供他人使用啦! 我之前也开源了很多组件库,如果大家对组件打包细节和构建流程有疑问,也可以参考我之前开源项目的方案。 发布到 npm 后的效果:


网络异常,图片无法展示
|


最后


如果大家对可视化搭建或者低代码/零代码感兴趣,也可以参考我往期的文章或者在评论区交流你的想法和心得,欢迎一起探索前端真正的技术。




目录
相关文章
|
12月前
|
前端开发 机器人 数据安全/隐私保护
Flutter笔记:手写并发布一个人机滑动验证码插件
写 Flutter 项目时,遇到需要滑块验证码功能。滑块验证码属于人机验证码的一种,看起来像是在一个图片中“挖去”了一块,然后通过用户手动操作滑块,让被“挖去”的部分移回来。由于我不想使用各种第三方模块,因此决定自己实现一个初版以后慢慢添砖加瓦。本文是对第一个版本的一点记录。
325 1
Flutter笔记:手写并发布一个人机滑动验证码插件
|
存储 前端开发 文件存储
Flutter笔记:关于应用程序中提交图片作为头像
1. 头像选择与提交的一般步骤Flutter笔记关于应用程序中提交图片作为头像作者目 录1. 头像选择与提交的一般步骤2. 选择本地文件到头像的示例代码3. 将图像提交到后端1. 头像选择与提交的一般步骤image将处理后的图像作为用户的头像显示在应用程序中。您可以使用Image或小部件来加载和显示图像。这些步骤涵盖了从选择图像到上传、处理和显示图像的基本流程。请根据您的具体需求和后端实现来自定义这些步骤。此外,确保您的应用程序有适当的权限以访问设备上的相册或相机,这通常需要在和。
270 0
|
Web App开发 前端开发 JavaScript
27 个前端动画库让你的交互更加炫酷
很多时候我们在开发前端页面时都会做一些动画效果来提升用户体验度和页面美观度,所以今天就来给大家推荐几个好用的JavaScript动画库,希望对各位小伙伴有所帮助!
2628 0
|
2月前
|
小程序 前端开发 JavaScript
微信小程序结合PWA技术,提供离线访问、后台运行、桌面图标及原生体验,增强应用性能与用户交互。
微信小程序结合PWA技术,提供离线访问、后台运行、桌面图标及原生体验,增强应用性能与用户交互。开发者运用Service Worker等实现资源缓存与实时推送,利用Web App Manifest添加快捷方式至桌面,通过CSS3和JavaScript打造流畅动画与手势操作,需注意兼容性与性能优化,为用户创造更佳体验。
56 0
|
3月前
|
Dart Android开发
Flutter-自定义短信验证码
Flutter-自定义短信验证码
31 1
|
5月前
|
监控 Dart
Dart编程技术分享:构建响应式屏幕监控软件界面
本文介绍了使用Dart和Flutter构建响应式屏幕监控软件的方法。首先,创建Dart项目并建立基本的Flutter应用结构,包括`MonitorScreen` widget。接着,引入HTTP库以获取服务器状态数据,并实现实时显示在界面上。最后,展示了如何在获取数据后自动提交到指定网址。通过这个教程,读者可以学习到构建监控界面及数据交互的基本步骤,为不同领域的监控需求提供便利。
110 1
|
5月前
|
NoSQL 安全 前端开发
验证码倒计时:用户界面的小细节,大智慧
本文深入探讨了验证码倒计时的设计和实现,一项看似简单但对用户体验影响深远的功能。我们将讨论为什么需要倒计时,如何在不同平台(如Web和移动应用)上实现它,以及如何确保它既用户友好又安全。无论你是前端新手还是资深开发者,理解验证码倒计时的原理和最佳实践都将有助于你创建更流畅、更安全的用户界面。
175 3
|
移动开发 JSON API
h5调起原生分享面板,展示更多功能方案
h5调起原生分享面板,展示更多功能方案
259 0
|
自然语言处理 JavaScript 前端开发
动态滑动图片验证码组件(支持多语言,移动端)
动态滑动图片验证码组件(支持多语言,移动端)
动态滑动图片验证码组件(支持多语言,移动端)
|
数据采集 JavaScript
Puppeteer + Nodejs 通用全屏网页截图方案(一)基本功能
学习一个网页截图程序的实现基本功能。