canvas 手写滑动验证

最近菜鸟公司要做一个滑动验证,但是看渡一里面说的是:如果是行为验证的话(自动化图灵测试 ------ 一定是服务端验证),后端需要搜集很多东西,包括用户的鼠标轨迹、点击速度等行为,反正就是很复杂,最好上框架,具体见:

www.bilibili.com/video/BV1Re...

但是菜鸟公司不用做这么复杂,就是前端滑动一下,免得别人登录太快的问题,减少攻击次数(其实只是领导感觉有用,其实屁用没有,就是给客户感觉牛皮的样子,手动狗头)!

思路

两个canvas:canvas1上画一张图并用两个圆覆盖在上面(一个正确、一个错误),然后canvas2只画正确的圆覆盖住的那部分图片,然后滑块操作的就是移动整个canvas2,只要停止的X坐标和我设置的正确的X坐标符合一种关系,就验证通过!

实现

菜鸟感觉自己的代码和注释都写得挺好的,这里直接上代码

js 复制代码
<script setup>
import { ElMessage } from "element-plus";
import { onMounted } from "vue";

const props = defineProps({
  // 外面可以传误差值
  mistakeValue: {
    type: Number,
    default: 3
  }
});

const emit = defineEmits(["pass"]);

// 随机图片
let imgurl = "https://picsum.photos/300/200";

// 绑定滑块值
let value1 = ref(0);
let movecanvas = ref(null);
let loading = ref(false);

// 保存数据,方便验证失败重绘
let rightX, r1, r2, r1y, r2y, errorX;

// 生成一个随机数 30 - 270之间(不能超出边界)
const random = (min = 31, max = 269) => {
  return Math.floor(Math.random() * (max - min + 1)) + min;
};

const init = () => {
  loading.value = true;
  // 保存正确的偏移值
  rightX = random();
  // 两个圆的半径
  r1 = random(20, 30);
  r2 = r1 - 5;
  // 随机生成两个圆的y坐标(不能超出边界)
  r1y = random(31, 169);
  r2y = random(31, 169);
  // 生成一个错误的偏移值,值在30-rightX之间,或者rightX - 270之间
  errorX =
    rightX + r1 > 200
      ? random(31, rightX - r1 - r2) // 在30到rightX之间
      : random(rightX + r1 + r2, 269); // 在rightX到270之间
};
init();

// 画:验证图
const dorwVerify = () => {
  const canvas = document.querySelector("#verify");
  const ctx = canvas.getContext("2d");
  const img = new Image();
  img.src = imgurl;
  img.onload = () => {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

    // 在canvas上形成两个浅灰色的圆,并覆盖在图片上
    ctx.beginPath();
    ctx.arc(rightX, r1y, r1, 0, 2 * Math.PI);
    ctx.fillStyle = "#eee";
    ctx.fill();
    ctx.beginPath();
    ctx.arc(errorX, r2y, r2, 0, 2 * Math.PI);
    ctx.fillStyle = "#eee";
    ctx.fill();
  };
};

// 画:滑块
const drowMove = () => {
  movecanvas.value = document.querySelector("#move");
  const movectx = movecanvas.value.getContext("2d");
  const moveimg = new Image();
  moveimg.src = imgurl;
  moveimg.onload = () => {
    movecanvas.value.width = moveimg.width;
    movecanvas.value.height = moveimg.height;
    // 创建圆形路径
    movectx.beginPath();
    movectx.arc(rightX, r1y, r1, 0, 2 * Math.PI);
    movectx.closePath();
    // 裁剪画布
    movectx.clip();
    // 只绘制裁剪区域内的图片
    movectx.drawImage(moveimg, 0, 0, movecanvas.value.width, movecanvas.value.height);

    movecanvas.value.style.left = -rightX + r1 + "px";

    loading.value = false;
  };
};

onMounted(() => {
  dorwVerify();
  drowMove();
});

// 移动设置left
const getvalFun = () => {
  const movecanvas = document.querySelector("#move");
  movecanvas.style.left = -rightX + r1 + (value1.value / 100) * 400 + "px";
};

// 验证
const passFun = () => {
  // 减100:是减去canvas1旁边留给滑块显示的距离
  const stopX = (value1.value / 100) * 400 - 100 + r1;
  if (Math.abs(stopX - rightX) < props.mistakeValue) {
    ElMessage.success("验证成功");
    emit("pass");
  } else {
    ElMessage.error("请重新验证");
    value1.value = 0;
    init();
    dorwVerify();
    drowMove();
  }
};

// 刷新
const refreshFun = () => {
  value1.value = 0;
  init();
  dorwVerify();
  drowMove();
};
</script>

<template>
  <div class="w-[400px]" v-loading="loading">
    <div class="relative">
      <canvas class="absolute h-[200px] w-[300px]" id="move"></canvas>
      <canvas class="ml-[100px] h-[200px] w-[300px]" id="verify"></canvas>
    </div>
    <div>
      <el-slider
        :step="0.1"
        :show-tooltip="false"
        v-model="value1"
        @input="getvalFun"
        @change="passFun"
      />
    </div>
    <div>
      <el-button plain type="primary" @click="refreshFun">看不清,换一张</el-button>
    </div>
  </div>
</template>

注意

  1. 这里推荐一个自动生成图片的网站:picsum.photos/ ,在链接后面指定宽度和高度就可以获取对应大小的随机图片,还能指定模糊、灰度等,真的非常的实用!

  2. 这里菜鸟生成errorX的地方其实可以优化一下,这里感觉还是差点意思!

  3. 这里画滑块的部分,菜鸟以为会有白色背景,但是其实没有!

实现结果

更多canvas常见操作见:渡一学习笔记:canvas、css滤镜、特效、svg

其他思路

其实菜鸟做完之后发现,如果按照菜鸟的思路来,甚至都不用canvas,直接:滑块用背景定位、验证图也是直接显示img,然后上面盖一个圆就行!

相关推荐
齐尹秦11 分钟前
HTML5 新的 Input 类型学习笔记
前端·html5
还是鼠鼠1 小时前
Node.js 如何发布一个 NPM 包——详细教程
前端·vscode·npm·node.js
kiro_10231 小时前
【ESP32S3】esp32获取串口数据并通过http上传到前端
前端·网络协议·http
鎏年_3 小时前
Vue2项目打包后,某些图片被转换为base64导致无法显示
前端·javascript·vue.js
康6204 小时前
vue2中引入elementui
前端·javascript·elementui
听风说雨的人儿4 小时前
ElementUI时间选择、日期选择
前端·javascript·elementui
wfsm6 小时前
React多层级对象改变值--immer
前端·javascript·react.js
沐土Arvin6 小时前
Chrome Performance 面板完全指南:从卡顿到丝滑的终极调试术
前端
少年姜太公8 小时前
一个半小时的腾讯一面,人麻了
前端·javascript·面试
Jiaberrr8 小时前
Vue3 实战:基于 mxGraph 与 WebSocket 的动态流程图构建
前端·javascript·vue.js·websocket·流程图