Kros的博客 Kros的博客
首页
  • CSS
  • 工具
  • Vue
  • js
  • Vue3
  • 算法
  • 折腾笔记
一言
  • 分类
  • 标签
  • 归档
码云

Kros

凡心所向,素履以往,生如逆旅,一苇以航
首页
  • CSS
  • 工具
  • Vue
  • js
  • Vue3
  • 算法
  • 折腾笔记
一言
  • 分类
  • 标签
  • 归档
码云
  • CSS

  • JavaScript

  • 工具

  • Vue

    • vue批量引入组件
    • vue中watch用法
    • vue使用基础JSX语法
    • vue首页白屏
    • vue长列表优化
    • 模板文件中script、template、style存在必要性
    • popup弹窗组件
    • 使用$mount和extend扩展vue
    • 扩展toast合并popup(按需加载)
    • canvas实现弧形进度条
    • vue中使用CSS Modules
    • vue实现无限轮播图
    • vue半圆形菜单
    • 纯CSS实现圆形进度条
    • v-model简单实现
    • vue带箭头下拉框
    • vue事件修饰符
    • vue鼠标悬浮显示提示文字
    • vue添加发布版本号方法
    • vue配置全局样式文件
    • vue在env development中设置全局变量不生效
    • vue引入less或scss全局变量
    • vue中使用Keepalive
    • vue2为什么只支持一个根节点
    • vue打包优化分析工具—webpack-bundle-analyzer
    • vue使用echarts(按需加载)
    • echarts柱形图重合堆叠
    • vue使用draggable实现多列拖拽(解决空列不能拖拽问题)
    • vue禁止三方库打包到bundle中
    • npm run serve运行背后的思考
    • vxe-table树结构不允许insert
    • vue组件延迟加载
    • 多页面应用配置
    • vue项目常用优化
    • vue实现拖动拼图验证码
    • vue虚拟dom
    • canvas心形动画
    • canvas绘制玫瑰曲线
    • element表单validateField验证部分字段
    • element日期选择限制今天以后并精确到小时
    • 前端使用JSEncrypt和node-rsa进行rsa加密传输和接收解密
    • elementui日期选择器生日只选择月和日不选择年并隐藏年份
    • el-upload组件第二次点击手动submit时不生效
    • element表单内输入框使用@keyup enter native回车时会刷新页面
  • antdv踩坑记录

  • Vue3

  • 前端
  • Vue
kros
2025-08-22

vue实现拖动拼图验证码

verify

现在许多网站的登录校验都加上了图片校验,这里用vue实现一个简单图形验证。如上图,我们需要有两个图层,不可拖动的底图和可拖动的拼图,使用canvas模拟两个图层。

# 绘制底图

使用ctx.drawImage绘制底图图片,使用ctx.getImageData获取随机拼图图片数据。随机拼图切图位置ry固定在图中心,rx在底图范围内随机 代码如下:

import imgSrc from './assets/img.png';
const width = ref(400);
const height = ref(250);

// 图形验证码
const rect = 50
let rx = 0//Math.max(rect * 2, Math.floor(Math.random() * (width.value - rect)))
const ry = height.value / 2 - rect / 2
let graphicImgData = null
const initImg = () => {
  const img = new Image();
  img.onload = () => {
    drawImg(img);
  };
  img.src = imgSrc;
}

const drawImg = (img) => {
  const ctx = canvasBack.value.getContext('2d');
  // 清除Canvas
  ctx.clearRect(0, 0, width.value, height.value);

  // 计算缩放后的尺寸
  const scaledWidth = img.width;
  const scaledHeight = img.height;
  
  // 计算绘制位置(居中显示)
  const x = (width.value - scaledWidth) / 2;
  const y = (height.value - scaledHeight) / 2;

  // 绘制图片
  ctx.drawImage(img, x, y, scaledWidth, scaledHeight);

  // 获取随机的图形验证块
  graphicImgData = ctx.getImageData(rx, ry, rect, rect);

  // 绘制图形验证块阴影
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
  ctx.shadowBlur = 15;
  ctx.fillStyle = 'rgba(255, 255, 255, 0.566)';
  ctx.fillRect(rx, ry, rect, rect);

	// 绘制拖动拼图
  // drawGraphic()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 绘制拖动拼图

使用ctx.putImageData将上面获取的图片数据绘制到第二个canvas上,代码如下:

const gx = ref(0)
let gy = ry

const drawGraphic = () => {
  
  const ctxGraphic = canvasGraphic.value.getContext('2d');
  ctxGraphic.clearRect(0, 0, width.value, height.value);
  ctxGraphic.shadowColor = "rgba(255, 255, 255)";
  ctxGraphic.shadowBlur = 10;
  ctxGraphic.putImageData(graphicImgData, gx.value, gy);
  
  ctxGraphic.shadowColor = "rgba(0, 0, 0, 0.5)";
  ctxGraphic.shadowBlur = 15;
  ctxGraphic.lineWidth = 0.2;
  ctxGraphic.strokeStyle = 'white'
  ctxGraphic.strokeRect(gx.value, gy, rect, rect);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 拖动拼图

  • 鼠标按下时记录移动状态
  • 鼠标移动时更新拼图位置并刷新canvas拼图
  • 鼠标抬起或移开时记录移动结束并检测校验是否验证成功
const moveStatus = ref(MoveStatus.value.NONE)

const onmousedown = (e) => {
  if (moveStatus.value != MoveStatus.value.NONE) return;
  console.log('onmousedown')
  moveStatus.value = MoveStatus.value.MOVE
  mStart = e.clientX;
}

const onmousemove = (e) => {
  if (moveStatus.value === MoveStatus.value.MOVE) {
    const moveX = e.clientX - mStart;
    mStart = e.clientX;
    gx.value = Math.min(Math.max(gx.value + moveX, 0), width.value - rect);
    drawGraphic()
  }  
}

const onmouseup = () => {
  moveStatus.value = MoveStatus.value.NONE;
  checkMoveStatus()
}

const onmouseleave = () => {
  moveStatus.value = MoveStatus.value.NONE;
  checkMoveStatus()
}

const checkMoveStatus = () => {
  if (Math.abs(gx.value - rx) <= 3) {
    moveStatus.value = MoveStatus.value.SUCCESS;
  } else {
    moveStatus.value = MoveStatus.value.FAILURE;
    // 重置位置
    // gx.value = 0;
    // drawGraphic();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 完整代码

完整代码

上次更新: 2025/09/05, 8:09:00
vue项目常用优化
vue虚拟dom

← vue项目常用优化 vue虚拟dom→

最近更新
01
Find the next perfect square
09-05
02
Regex validate PIN code
09-05
03
Find the odd int
09-05
更多文章>
Theme by Vdoing | Copyright © 2020-2025 kros king
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
icon-heart-o icon-heart icon-infinity icon-pause icon-play link next prev