赞
踩
验证码我们平时应该不少见到,其原理就是通过HTML5 Canvas API也就是我们说的画布,我们可以通过算法随机生成一系列字符、数字或图形,并将它们以扭曲、拉伸、旋转、添加噪点,以增加机器识别难度。对于更高级的验证码,可以使用SVG或者其他矢量图技术来创建复杂且难以解析的图形。
总的来说主要分为以下五步:
下面我们分别从上面五步来实现一个验证码组件
<template>
<div class="img-verify">
<!-- 画布,绑定一个点击事件,用于后续验证码的刷新 -->
<canvas ref="verify" :width="width" :height="height" @click="handleDraw"></canvas>
</div>
</template>
const state = reactive({
str: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", // 字符串
imgCode: "", // 初始化验证码为空
});
// 随机数
const randomNum = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min) + min);
};
// 随机颜色
const randomColor = (min: number, max: number) => {
const r = randomNum(min, max);
const g = randomNum(min, max);
const b = randomNum(min, max);
return `rgb(${r},${g},${b})`;
};
// 定义一个绘制验证码的函数 draw const draw = () => { // 获取 canvas 上下文,用于绘图 const ctx = verify.value.getContext("2d"); // 生成背景颜色,设置为浅色调(RGB范围在200-230之间) ctx.fillStyle = randomColor(200, 230); // 填充整个 canvas 背景区域 ctx.fillRect(0, 0, state.width, state.height); // 初始化验证码字符串变量 imgCode let imgCode = ""; // 遍历4次以绘制4个随机字符 for (let i = 0; i < 4; i++) { // 随机选取字符集中的一个字符 const text = state.str[randomNum(0, state.str.length)]; // 将该字符添加到验证码字符串中 imgCode += text; // 设置随机字体大小(18px - 40px) const fontSize = randomNum(18, 40); // 设置随机旋转角度(-30度至30度) const deg = randomNum(-30, 30); // 设置字体样式、大小和颜色 ctx.font = `${fontSize}px Simhei`; ctx.textBaseline = "top"; ctx.fillStyle = randomColor(80, 150); // 保存当前画布状态,以便进行平移、旋转等操作而不影响后续绘制 ctx.save(); // 对每个字符进行坐标平移,并根据旋转角度进行旋转 ctx.translate(30 * i + 15, 15); ctx.rotate((deg * Math.PI) / 180); // 在当前位置绘制填充的文本(偏移量防止超出边框) ctx.fillText(text, -15 + 5, -15); // 恢复之前保存的画布状态,撤销平移和旋转 ctx.restore(); } // 绘制5条随机干扰线,颜色设置为较浅色系 for (let i = 0; i < 5; i++) { ctx.beginPath(); ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height)); ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height)); ctx.strokeStyle = randomColor(180, 230); ctx.closePath(); // 这一行实际上在绘制直线时是可选的 ctx.stroke(); // 实际上绘制出干扰线 } // 绘制40个随机位置的小圆点作为干扰元素,颜色同样设置为较浅色系 for (let i = 0; i < 40; i++) { ctx.beginPath(); // 创建一个圆形路径,指定圆心坐标与半径 ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI); ctx.closePath(); // 同样,在绘制封闭图形如圆时是可选的 // 设置小圆点的颜色 ctx.fillStyle = randomColor(150, 200); // 填充该圆形路径,形成实心小圆点 ctx.fill(); } // 返回生成的验证码字符串 return imgCode; };
这里我重点解释一下上述ctx.save()
和ctx.restore()
的用法。它们在HTML5 Canvas API中,是两个非常重要的方法,用于保存和恢复Canvas画布的状态。
save() 方法:
当调用 ctx.save()
时,Canvas会将当前的绘图环境状态(包括但不限于以下内容)保存到一个内部的栈结构中:
restore() 方法:
ctx.restore()
时,Canvas会从这个内部栈中弹出最近一次保存的状态,并将其设置为当前的绘图环境状态。save()
存储的所有属性都会被还原到保存时的状态,取消掉在这两者之间所做的任何改变。简单来说,当你在绘制过程中需要对画布执行一些临时性的操作(如进行复杂的变换或临时更改颜色),但不希望这些操作影响后续的绘制时,可以先调用 save()
保存当前状态,然后执行所需的操作。完成这部分绘制后,调用 restore()
来确保画布状态回到之前的点,这样下一次绘制就会不受刚才那些临时操作的影响。
举个例子,在上述验证码生成代码片段中,每次循环绘制字符之前都调用了 ctx.save()
,目的是为了旋转和定位每个字符而不影响其他字符的位置和角度。字符绘制完成后使用 ctx.restore()
恢复画布原始状态,以便下一个字符能够按照新的随机位置和角度独立绘制。
<template> <div class="img-verify"> <!-- 画布,绑定一个点击事件,用于刷新验证码 --> <canvas ref="verify" :width="width" :height="height" @click="handleDraw"></canvas> </div> </template> <script> import { reactive, onMounted, ref, toRefs } from "vue"; export default { setup() { const verify = ref(null); const state = reactive({ str: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", // 字符串 width: 110, height: 40, imgCode: "", // 初始化验证码为空 }); onMounted(() => { if (verify.value) { // 初始化绘制图片验证码 state.imgCode = draw(); } }); // 点击图片重新绘制 const handleDraw = () => { state.imgCode = draw(); }; // 随机数 const randomNum = (min: number, max: number): number => { return Math.floor(Math.random() * (max - min) + min); }; // 随机颜色 const randomColor = (min: number, max: number) => { const r = randomNum(min, max); const g = randomNum(min, max); const b = randomNum(min, max); return `rgb(${r},${g},${b})`; }; // 定义一个绘制验证码的函数 draw const draw = () => { // 获取 canvas 上下文,用于绘图 const ctx = verify.value.getContext("2d"); // 生成背景颜色,设置为浅色调(RGB范围在200-230之间) ctx.fillStyle = randomColor(200, 230); // 填充整个 canvas 背景区域 ctx.fillRect(0, 0, state.width, state.height); // 初始化验证码字符串变量 imgCode let imgCode = ""; // 遍历4次以绘制4个随机字符 for (let i = 0; i < 4; i++) { // 随机选取字符集中的一个字符 const text = state.str[randomNum(0, state.str.length)]; // 将该字符添加到验证码字符串中 imgCode += text; // 设置随机字体大小(18px - 40px) const fontSize = randomNum(18, 40); // 设置随机旋转角度(-30度至30度) const deg = randomNum(-30, 30); // 设置字体样式、大小和颜色 ctx.font = `${fontSize}px Simhei`; ctx.textBaseline = "top"; ctx.fillStyle = randomColor(80, 150); // 保存当前画布状态,以便进行平移、旋转等操作而不影响后续绘制 ctx.save(); // 对每个字符进行坐标平移,并根据旋转角度进行旋转 ctx.translate(30 * i + 15, 15); ctx.rotate((deg * Math.PI) / 180); // 在当前位置绘制填充的文本(偏移量防止超出边框) ctx.fillText(text, -15 + 5, -15); // 恢复之前保存的画布状态,撤销平移和旋转 ctx.restore(); } // 绘制5条随机干扰线,颜色设置为较浅色系 for (let i = 0; i < 5; i++) { ctx.beginPath(); ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height)); ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height)); ctx.strokeStyle = randomColor(180, 230); ctx.closePath(); // 这一行实际上在绘制直线时是可选的 ctx.stroke(); // 实际上绘制出干扰线 } // 绘制40个随机位置的小圆点作为干扰元素,颜色同样设置为较浅色系 for (let i = 0; i < 40; i++) { ctx.beginPath(); // 创建一个圆形路径,指定圆心坐标与半径 ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI); ctx.closePath(); // 同样,在绘制封闭图形如圆时是可选的 // 设置小圆点的颜色 ctx.fillStyle = randomColor(150, 200); // 填充该圆形路径,形成实心小圆点 ctx.fill(); } // 返回生成的验证码字符串 return imgCode; }; return { ...toRefs(state), // toRefs为了防止结构数据丢失响应性 verify, handleDraw, }; }, }; </script> <style> /* 设置鼠标悬停样式 */ .img-verify canvas { cursor: pointer; } </style>
HHTML: <!-- 模版,点击刷新验证码 --> <div class="imgCode"> <VueImgVerify ref="verifyRef" /> </div> JS: // 引入组件 import VueImgVerify from '@/components/VueImgVerify.vue'; // 便于拿到 verifyRef 组件内的实例属性 const verifyRef = ref<any>(null); // 这里是我项目中点击注册时会触发的验证,具体以自己的代码来写 const onSubmit = async () => { // 使用 nextTick 确保组件渲染后再访问 verifyRef await nextTick(); // 确保 verifyRef 不为 null if (verifyRef.value) { // 生成的图片验证码的文字等于验证码组件生成的验证码 state.imgCode = verifyRef.value.imgCode || ""; // 如果验证码组件生成的验证码的小写 != 用户输入的验证码的小写,则提示错误 if ( verifyRef.value.imgCode.toLowerCase() != state.verify.toLowerCase() ) { console.log("verifyRef.value.imgCode", verifyRef.value.imgCode); showFailToast("验证码错误"); console.log("Generated Captcha:", verifyRef.value.imgCode.toLowerCase()); console.log("User Input Captcha:", state.verify.toLowerCase()); return; } // 验证码匹配成功,注册=>注册成功 // 发请求,将state.nickname,state.username,state.password数据传给后端 await axios.post('/register', { nickname: state.nickname, username: state.username, password: state.password, isVip: state.isVip, userType: state.userType }); showSuccessToast('注册成功!'); // 1s后跳转到登录页面 setTimeout(() => { router.push('/login'); }, 1000); } else { // 处理 verifyRef 为 null 的情况 console.error("verifyRef is null"); } };
最终效果:
附件资料:点此下载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。