前端图形学实战: 从零开发一款轻量级滑动验证码组件(vue3 + vite版)

简介: 前端图形学实战: 从零开发一款轻量级滑动验证码组件(vue3 + vite版)


今天和大家分享一个新的图形学实战——滑动验证码的实现

你将收获

  • vue3组件的设计思路和技巧
  • canvas的常用绘图api用法
  • 滑动验证码组件的基本设计原理和实现过程
  • 如何从零发布vue3组件库

效果演示

按照笔者的写作习惯, 这里先和大家演示一下实现的效果:

image.png

正文

这篇文章我会带大家使用 vue3 + vite 来实现滑动验证码, 当然笔者之前也实现了基于 react 的滑动验证码组件, 感兴趣的可以参考我之前的文章:

同时也可以在 github 上查看到对应的实现代码:

github.com/MrXujiang/v…

技术实现

在着手开发组件之前我们需要提前明确组件的设计需求, 并制定合理的开放 api, 这里分享一下我总结的组件优雅设计的原则:

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

而我们滑动验证码组件要想让更多的人使用, 就必须做到一定程度的灵活可配置, 接下来是我总结的滑动验证码的核心 api:

  • 控制滑动验证码的显示隐藏(visible)
  • 控制滑动验证码的大小(width 和 height)
  • 控制滑块的样式(边长 l 和半径 r)
  • 滑块的提示文本(text)
  • 滑动验证码的图片数据源(imgUrls)
  • 验证成功的事件回调(onSuccess)
  • 验证失败的事件回调(onFail)
  • 用户自定义的验证逻辑(onCustomVertify)
  • 用户拖拽滑块时的回调(onDraw)
  • 刷新图片时的回调(onRefresh)

对应图示如下:

image.png

属性功能设计好之后我们来构想一下在vue项目中具体的使用方式:

<template>
  <div>
    <vertify 
      width="200" 
      height="80" 
      l="50" 
      r="5"
    ></vertify>
  </div>
</template>
<script setup>
// 业务逻辑...
</script>

以上基础准备好之后我们来可看一下滑动验证码的具体交互流程:

image.png

1.设计滑动验证码的基本骨架

这里我以vue3组合函数的方法来定义一下 vue-slider-veritfy 组件骨架:

<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
interface VertifyType {
  spliced: boolean;
  verified: boolean; // 简单验证拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
  left: number; // 滑块的移动位置
  destX: number; // 滑块的目标位置
}
interface IProps {
  width?: number;
  visible?: boolean;
  height?: number;
  refreshIcon?: string;
  l?: number;
  r?: number;
  imgUrl?: string;
  text?: string;
  /**
   * @description   拖拽滑块时的回调, 参数为当前滑块拖拽的距离
   * @default       (l: number):void => {}
   */
  onDraw?: (l: number) => {};
  /**
   * @description   用户的自定义验证逻辑
   * @default       (arg: VertifyType) => VertifyType
   */
  onCustomVertify?: (arg: VertifyType) => VertifyType;
  /**
   * @description   重制刷新前的回调
   * @default       ():void => {}
   */
  onBeforeRefresh?: () => void;
  /**
   * @description   验证成功回调
   * @default       ():void => {}
   */
  onSuccess?: VoidFunction;
  /**
   * @description   验证失败回调
   * @default       ():void => {}
   */
  onFail?: VoidFunction;
  /**
   * @description   刷新时回调
   * @default       ():void => {}
   */
  onRefresh?: VoidFunction;
}
// 定义默认值
const props = withDefaults(defineProps<IProps>(), {
  width: 320,
  visible: true,
  height: 160,
  refreshIcon: "http://cdn.dooring.cn/dr/icon12.png",
  l: 42,
  r: 9,
  imgUrl: "",
  text: "",
});
// 构建dom结构
<template>
  <div
    className="vertifyWrap"
  >
    <div className="canvasArea">
      <canvas ref="canvasRef"></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"
    ></div>
    <div
      className="loadingContainer"
    >
      <div className="loadingIcon"></div>
      <span>加载中...</span>
    </div>
  </div>
</template>

上面代码需要注意的就是 withDefaultsdefineProps , defineProps 主要用来定义 vue 组件的属性类型, withDefaults 可以用来定义组件属性的默认值, 这也是在设计 vue3 组件中常用的 api

2. 滑动验证码核心功能实现

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

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

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

image.png

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

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

image.png

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

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

由于实现方式比较固定, 偏 canvas 语法层, 这里直接分享一下代码:

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);
  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.7)";
  ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
  ctx.stroke();
  ctx.globalCompositeOperation = "destination-over";
  operation === "fill" ? ctx.fill() : ctx.clip();
};

这里需要补充的一点是 canvasglobalCompositeOperation 属性,它的主要目的是设置如何将一个源(新的)图像绘制到目标(已有)的图像上。

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

w3c上有个形象的例子:

image.png

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

image.png

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

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

接下来我们只需要用 javascript 实现随机图片和随机位置即可。

我们再来实现一下镂空效果:

const blockCtx = blockRef.value.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.value - 3, y1, L, L)
// 调整滑块画布宽度
blockRef.value.width = L
blockCtx.putImageData(ImageData, 0, y1)

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

image.png

实现滑块移动和验证逻辑

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

  • onMouseDown
  • onMouseMove
  • onMouseUp

image.png

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

const handleDragMove = (e) => {
    if (!isMouseDownRef.value) 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.value
    const moveY = eventY - originYRef.value
    if (moveX < 0 || moveX + 36 >= width) return false
    sliderLeft.value = moveX;
    const blockLeft = (width - l - 2r) / (width - l) * moveX
    blockRef.value.style.left = blockLeft + 'px'
}

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

我们发现 vue3 版本的实现和我之前的 react 版本的方式类似, 主要区别就在于 api 用法和定义上, 这里分享一下具体的变量定义:

const {
  text,
  l,
  r,
  imgUrl,
  width,
  height,
  visible,
  onBeforeRefresh,
  onRefresh,
  onFail,
  onSuccess,
  onCustomVertify,
  onDraw,
} = props;
const isLoading = ref(false);
const sliderLeft = ref(0);
const sliderClass = ref("sliderContainer");
const textTip = ref(text);
const canvasRef = ref<any>(null);
const blockRef = ref<any>(null);
const imgRef = ref<any>(null);
const isMouseDownRef = ref<boolean>(false);
const trailRef = ref<number[]>([]);
const originXRef = ref<number>(0);
const originYRef = ref<number>(0);
const xRef = ref<number>(0);
const yRef = ref<number>(0);
const PI = Math.PI;
const L = l + r * 2 + 3; // 滑块实际边长

完整的实现效果如下:

image.png

将实现的滑动验证码组件发布到 npm 上

发布流程我在 从零开发一款轻量级滑动验证码插件 有详细的介绍, 接下来给大家展示一下通过 vite 打包组件包的配置:

export default defineConfig({
  ...baseConfig,
  build: {
    outDir: 'dist',
    lib: {
      entry: resolve(__dirname, '../packages/index.ts'),
      name: 'vue-slider-vertify',
      fileName: (format) => `vue-slider-vertify.${format}.js`,
    },
    rollupOptions: {
      // 确保外部化处理不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue'
        }
      }
    }
  },
  plugins: [
    ...(baseConfig as any).plugins,
    dts(),
  ]
});

打包后的 dist 目录结构如下:

image.png

如果大家想了解详细的源码和打包过程, 可以在 github 上查看完整代码, 地址如下:

github.com/MrXujiang/v…

如果大家想通过 npm 的方式直接使用, 可以用我已经发布到 npm 服务器的 react-slider-vertify, 按照如下方式安装和使用:

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

在代码里使用:

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')} 
        />
};

扩展

大家也可以基于自己的业务需求改造我们的验证码, 实现更负复杂的功能, 改造成多种形态的验证码, 也欢迎大家随时贡献, 一起打造非常有意思的验证码组件。

后期规划

后面会继续围绕图形可视化来实现更多有意思的应用, 比如3D可视化, 图形编辑器, 可视化图表等, 如果大家感兴趣, 可以参考我的github: gitee.com/lowcode-chi…

如果文章对你有帮助, 欢迎点赞评论, 让我们一起探索真正的前端技术。


目录
相关文章
|
4天前
|
前端开发 开发者 UED
前端只是切图仔?来学学给开发人看的UI设计
该文章针对前端开发者介绍了UI设计的基本原则与实践技巧,覆盖了布局、色彩理论、字体选择等方面的知识,并提供了设计工具和资源推荐,帮助开发者提升产品的视觉与交互体验。
|
8天前
|
开发框架 前端开发 JavaScript
【博客开发】前端应用开发环境搭建(可复用)
【博客开发】前端应用开发环境搭建(可复用)
|
13天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
29天前
|
缓存 监控 前端开发
前端性能优化实战:让你的网站快如闪电的十大秘籍
【9月更文挑战第3天】通过以上十大秘籍的实践,您可以显著提升网站的前端性能,让您的网站在竞争激烈的互联网环境中脱颖而出,为用户带来更加流畅和愉悦的体验。记住,前端性能优化是一个永无止境的过程,只有不断迭代和优化,才能保持网站的竞争力。
|
2月前
|
C# 前端开发 UED
WPF数据验证实战:内置控件与自定义规则,带你玩转前端数据验证,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据验证是确保输入正确性的关键环节。前端验证能及时发现错误,提升用户体验和程序可靠性。本文对比了几种常用的WPF数据验证方法,并通过示例展示了如何使用内置验证控件(如`TextBox`)及自定义验证规则实现有效验证。内置控件结合`Validation`类可快速实现简单验证;自定义规则则提供了更灵活的复杂逻辑支持。希望本文能帮助开发者更好地进行WPF数据验证。
49 0
|
2月前
|
前端开发 Java UED
JSF 面向组件开发究竟藏着何种奥秘?带你探寻可复用 UI 组件设计的神秘之路
【8月更文挑战第31天】在现代软件开发中,高效与可维护性至关重要。JavaServer Faces(JSF)框架通过其面向组件的开发模式,提供了构建复杂用户界面的强大工具,特别适用于设计可复用的 UI 组件。通过合理设计组件的功能与外观,可以显著提高开发效率并降低维护成本。本文以一个具体的 `MessageComponent` 示例展示了如何创建可复用的 JSF 组件,并介绍了如何在 JSF 页面中使用这些组件。结合其他技术如 PrimeFaces 和 Bootstrap,可以进一步丰富组件库,提升用户体验。
44 0
|
18天前
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
|
2月前
|
存储 前端开发 JavaScript
前端语言串讲 | 青训营笔记
前端语言串讲 | 青训营笔记
28 0
|
4月前
|
JSON 前端开发 JavaScript
前端Ajax、Axios和Fetch的用法和区别笔记
前端Ajax、Axios和Fetch的用法和区别笔记
74 2
|
4月前
|
前端开发 JavaScript 数据库
如何实现前后端分离-----前端笔记
如何实现前后端分离-----前端笔记