先上效果:
这里是前端生成的验证码,如果需要请求后端把随机数改为接口就好了。
使用的是vantUI的弹窗,当然核心还是canvas。我将它封装成组件了直接上代码:
<template>
<div class="component">
<van-dialog
ref="dialog"
v-model="showDialog"
title="请输入验证码"
:before-close="onBeforeClose"
>
<canvas ref="canvas" :width="width" :height="height" />
<van-field
style="margin-top: 20px"
v-model="decode"
label="验证码:"
placeholder="请输入验证码"
/>
</van-dialog>
</div>
</template>
<script>
import { Notify } from "vant";
export default {
props: {
width: {
type: Number,
default: 200,
},
height: {
type: Number,
default: 50,
},
length: {
// 生成长度
type: Number,
default: 4,
},
fontSize: {
type: Number,
default: 30,
},
},
data() {
return {
showDialog: false,
decode: "",
encode: "",
};
},
watch: {
showDialog(newVal, oldVal) {
if (newVal) {
this.$nextTick(() => {
this.drawCaptcha();
});
}
},
},
mounted() {},
methods: {
async onBeforeClose(action, done) {
if (action === "confirm") {
// 点击确定走这里
if (this.encode == this.decode.toUpperCase()) {
done(); // 关闭弹窗
this.decode = "";
} else {
Notify("验证码错误!");
return done(false); // 阻止弹窗关闭
}
} else {
done(); // 关闭弹窗
}
},
drawCaptcha() {
const canvas = this.$refs.canvas;
const context = canvas.getContext("2d");
// 清除画布
context.clearRect(0, 0, this.width, this.height);
// 计算字符之间的间距
const spacing = this.width / (this.length + 1);
// 随机生成指定长度的大写字母验证码
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 验证码
this.encode = "";
for (let i = 0; i < this.length; i++) {
const randomLetter =
alphabet[Math.floor(Math.random() * alphabet.length)];
this.encode += randomLetter;
// 设置字体和样式
context.font = `bold ${this.fontSize}px Arial`;
context.fillStyle = "#000";
// 随机旋转角度
const angle = Math.random() * 1 - 0.2;
// 平移画布以便绘制倾斜的字符,并应用间距
context.translate((i + 1) * spacing, this.height / 2);
context.rotate(angle);
// 在画布上绘制验证码
context.fillText(randomLetter, -12, 12);
// 恢复画布状态,以便绘制下一个字符
context.rotate(-angle);
context.translate(-((i + 1) * spacing), -(this.height / 2));
}
// 添加干扰元素
for (let i = 0; i < (this.width * this.height) / 100; i++) {
const x1 = Math.random() * this.width;
const y1 = Math.random() * this.height;
const lengthFactor = Math.random() * 20; // 控制线的长度范围
const x2 = x1 + Math.cos(Math.random() * Math.PI * 2) * lengthFactor;
const y2 = y1 + Math.sin(Math.random() * Math.PI * 2) * lengthFactor;
// 生成线
context.strokeStyle = this.getRandomColor();
context.lineWidth = 1;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
// 生成点
const x = Math.random() * this.width;
const y = Math.random() * this.height;
context.fillStyle = this.getRandomColor();
context.fillRect(x, y, 1, 1);
}
},
getRandomColor() {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
},
},
};
</script>
<style scoped lang="scss">
</style>
调用:
<DialogCode ref="dialogCode" />
// 调用
methods: {
showCode() {
this.$refs.dialogCode.showDialog = true;
},
}