前言
前面的文章写过使用Canvas如何实现滑动验证码、随机字符串验证码、计算验证码等,今天我们看看如何实现一个文字点选验证码。
之前的文章有写过Canvas系列小功能文章,有兴趣的可以瞧上一瞧~
Canvas系列-下雪特效
Canvas系列-签字功能
Canvas系列-滑动验证
Canvas系列-字符验证
Canvas系列-计算验证
实现随机字符串验证功能
效果图如下,源码链接
基本思路
1、绘制背景,根据传入的pops中的images列表属性随机选取一张图片
2、根据传入的fontStr字符串随机生成规定长度的字符数组,并保存下来
3、在canvas画布中绘制随机生成的字符,并且随机抽取文字组成新的数组,并且保存下来用于提示文字和验证
4、点击Canvas画布获取点选位置数据并用数组保存下来,然后根据定位渲染点选圆形
5、根据点选保存的信息和提示文字数组,按序判断点选的圆形边界是否在精度范围内,如果都边界判断都为真则表示验证通过
6、重置功能则清空所有保存的数据并从第1步重新开始
具体实现代码
// HTML
<div id="app" v-cloak>
<div class="verify-container" :style="{width: `${width}px`}">
<div class="refresh" @click="reset">
<svg t="1637315258145" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2420" width="20" height="20"><path d="M960 416V192l-73.056 73.056a447.712 447.712 0 0 0-373.6-201.088C265.92 63.968 65.312 264.544 65.312 512S265.92 960.032 513.344 960.032a448.064 448.064 0 0 0 415.232-279.488 38.368 38.368 0 1 0-71.136-28.896 371.36 371.36 0 0 1-344.096 231.584C308.32 883.232 142.112 717.024 142.112 512S308.32 140.768 513.344 140.768c132.448 0 251.936 70.08 318.016 179.84L736 416h224z" p-id="2421" fill="#8a8a8a"></path></svg>
</div>
<div class="pic">
<canvas class="canvas" ref="canvas" :width="width" :height="height" @click="createPointer"></canvas>
<span class="pointer"
v-for="(item, index) in pointer"
:style="{left: `${item.x}px`, top: `${item.y}px`}">
<i>{{ index + 1 }}</i>
</span>
</div>
<div :class="['toolbar', state]">
<p v-if="state==='fail'">验证失败</p>
<p v-else-if="state==='success'">验证通过</p>
<p v-else>请顺序点击【<span v-for="(item, index) in tips" :key="index">{{ item.character }}</span>】</p>
</div>
</div>
<br>
</div>
// JS
const App = {
props: {
width: {
type: Number,
default: 320
},
height: {
type: Number,
default: 160
},
fontStr: {
type: String,
default: '赵钱孙李周吴郑王朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐'
},
fontNum: { // 显示几个
type: Number,
default: 5
},
checkNum: { // 点击验证数
type: Number,
default: 3
},
accuracy: { // 精度
type: Number,
default: 15
},
images: {
type: Array,
default: [
'https://img2.baidu.com/it/u=172118635,3843440198&fm=26&fmt=auto',
'https://img2.baidu.com/it/u=2726247805,538885610&fm=26&fmt=auto',
'https://img1.baidu.com/it/u=1078976348,1462740125&fm=26&fmt=auto'
]
}
},
data() {
return {
bgImg: null, // 背景图
ctx: null, // 背景画笔
fontArr: [], // 显示的字符
tips: [], // 提示文字
pointer: [], // 点击序号
state: '' , // success fail active
timeIns: null,
}
},
mounted () {
this.init()
},
beforeDestroy () {
clearTimeout(this.timeIns)
},
methods: {
init () {
this.ctx = this.$refs['canvas'].getContext('2d');
this.getImg();
},
getImg () {
const img = document.createElement('img');
const imagesLen = this.images.length;
const randomIndex = Math.floor(Math.random() * imagesLen);
img.crossOrigin = "Anonymous";
img.src = this.images[randomIndex];
this.bgImg = img;
img.onload = () => {
console.log('图片加载完成')
this.draw();
}
console.log(this.bgImg)
},
draw () {
// 绘制背景图
this.ctx.drawImage(this.bgImg, 0, 0, this.width, this.height);
for (let i = 0; i < this.fontNum; i++) {
const character = this.getRandomCharacter();
console.log(character)
const fontSize = this.randomNum(20, this.height * 1 / 4);
const fontWeight = Math.random() > 0.5 ? 'bold' : 'normal';
const fontStyle = Math.random() > 0.5 ? 'italic' : 'normal';
const fontFamily = Math.random() > 0.5 ? 'sans-serif' : 'serif'
const x = this.width / this.fontNum * i + 10;
const y = Math.random() * (this.height - fontSize);
this.ctx.fillStyle = this.randomColor(0, 255);
this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
this.ctx.textBaseline = 'top';
this.ctx.fillText(character, x, y);
this.fontArr.push({
character,
// fontSize,
x,
y
})
}
console.log(this.fontArr)
for (let i = 0; i < this.checkNum; i++) {
const randomIndex = Math.floor(Math.random() * this.fontArr.length);
const character = this.fontArr.splice([randomIndex], 1)[0];
this.tips.push(character);
// console.log(character, this.fontArr)
}
console.log(this.tips)
},
// 获取随机字符
getRandomCharacter () {
const fontStrLen = this.fontStr.length;
const randomIndex = Math.floor(Math.random() * fontStrLen);
const character = this.fontStr.charAt([randomIndex]);
// debugger
const isSome = this.fontArr.some(item => {
return item.character === character;
})
if (isSome) {
console.log(`>>>${character}已存在>>>`)
return this.getRandomCharacter();
} else {
return character;
}
},
randomColor (min, max) {
let r = this.randomNum(min, max)
let g = this.randomNum(min, max)
let b = this.randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
},
randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
createPointer (e) {
// console.log(e)
const canvasRect = this.$refs.canvas.getBoundingClientRect();
const x = e.offsetX - 15;
const y = e.offsetY - 15;
if (this.pointer.length < this.tips.length) {
this.pointer.push({x, y});
// console.log(this.pointer.length)
// this.verify()
this.state = 'active'
}
if (this.pointer.length === this.tips.length) {
const isPass = this.verify();
if (isPass) {
this.state = 'success';
} else {
this.state = 'fail';
// 如果失败则1000毫秒后重置
this.timeIns = setTimeout(() => {
this.reset()
}, 1000)
}
}
},
// 判断精度
verify () {
console.log("验证")
const result = this.pointer.every((item, index) => {
const _left = item.x > this.tips[index].x - this.accuracy;
const _right = item.x < this.tips[index].x + this.accuracy;
const _top = item.y > this.tips[index].y - this.accuracy;
const _bottom = item.y < this.tips[index].y + this.accuracy;
return _left && _right && _top && _bottom;
})
console.log(result)
return result;
},
// 重置
reset () {
this.fontArr = [];
this.tips = [];
this.pointer = [];
this.state = '';
this.ctx.clearRect(0, 0, this.width, this.height);
this.getImg();
}
}
}
Vue.createApp(App).mount('#app');
解析:上面就是布局和所有js代码
结尾
上面就是【文字点选验证码】的实现原理,代码是使用vue编写的小demo,可能存在一些兼容性问题,也没有封装成组件,但是具有一些参考意义,用于生产可以自己去封装成组件使用,完整的代码在我的GitHub仓库
本文是笔者总结编撰,如有偏颇,欢迎留言指正,若您觉得本文对你有用,不妨点个赞~