前端图形学实战: 从零开发一款轻量级滑动验证码组件(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…

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


目录
相关文章
|
5天前
|
前端开发 JavaScript 安全
揭秘!前端大牛们如何高效解决跨域问题,提升开发效率!
【10月更文挑战第30天】在Web开发中,跨域问题是一大挑战。本文介绍前端大牛们常用的跨域解决方案,包括JSONP、CORS、postMessage和Nginx/Node.js代理,对比它们的优缺点,帮助初学者提升开发效率。
22 4
|
8天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
21 2
|
9天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
23 2
|
13天前
|
前端开发 JavaScript 开发工具
Vite 4.0 发布,下一代的前端工具链
【10月更文挑战第21天】Vite 4.0 的发布标志着前端开发领域的又一次重要进步。它为开发者带来了更高效、更智能、更具创新性的开发体验,正逐渐成为下一代前端工具链的引领者。
|
14天前
|
存储 缓存 算法
前端算法:优化与实战技巧的深度探索
【10月更文挑战第21天】前端算法:优化与实战技巧的深度探索
13 1
|
15天前
|
人工智能 资源调度 数据可视化
【AI应用落地实战】智能文档处理本地部署——可视化文档解析前端TextIn ParseX实践
2024长沙·中国1024程序员节以“智能应用新生态”为主题,吸引了众多技术大咖。合合信息展示了“智能文档处理百宝箱”的三大工具:可视化文档解析前端TextIn ParseX、向量化acge-embedding模型和文档解析测评工具markdown_tester,助力智能文档处理与知识管理。
|
23天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
103 2
|
23天前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
34 0
|
23天前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
|
23天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。