前期准备
Canvas 教程 – Web API | MDN (mozilla.org)
了解Canvas元素如何使用
正式工作
创建文件
创建IdentifyCode.vue作为组件,为了显示验证码,我们首先要声明一个canvas元素
<template> <canvas id="code-canvas" :width="picWidth" :height="picHeight"></canvas> </template>
要做到下图的效果,我们首先要来进行一下元素的分析
该验证码由4个数字或字母组成,大小随机,旋转角度随机,颜色随机;背景颜色随机,有随机颜色的点状随机分布,随机干扰线。
一个验证码元素还具有长度和宽度两个属性,所以在data里面进行一下声明,便于后续函数使用。
data() { return { picWidth: 120, picHeight: 35, }; }, props: { code: { type: String, default: "2244", }, },
验证码充满了随机性,于是我们开始编写随机函数。
getRandomVal(min, max) { return Math.floor(Math.random() * (max - min) + min); } getRandomCol(min, max) { var r = this.getRandomVal(min, max); var g = this.getRandomVal(min, max); var b = this.getRandomVal(min, max); return "rgb(" + r + "," + g + "," + b + ")"; }
这里随机颜色返回的是rgb(255,255,255)这种格式的字符串。完成了随机生成数字和颜色后,接下来分步进行生成验证码的函数。
进行背景颜色的随机生成
drawBackground(context) { context.fillStyle = this.getRandomCol(180, 240); context.fillRect(0, 0, this.picWidth, this.picHeight); }
进行随机文本的填充
drawText(context) { for (let i = 0; i < this.code.length; i++) { context.fillStyle = this.getRandomCol(0, 180); context.font = this.getRandomVal(20, 40) + "px serif"; let deg = this.getRandomVal(-20, 20); let x = 20 * (i + 1); let y = this.getRandomVal(25, 32); context.translate(x, y); context.rotate((Math.PI / 180) * deg); context.fillText(this.code[i], 0, 0); context.rotate(-(Math.PI / 180) * deg); context.translate(-x, -y); } },
在这里要注意,是先对画布进行平移操作再进行旋转,因为画布旋转后再平移,会让画布以新的旋转后的参考系进行平移,在填充文本之后要注意恢复画布到正常的状态,为下一步生成文本做准备。
进行随机干扰点的生成
drawDot(context) { for (let i = 0; i < 20; i++) { context.fillStyle = this.getRandomCol(0, 255); context.beginPath(); context.arc( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight), 1, 0, Math.PI * 2 ); context.fill(); } },
arc(x, y, radius, startAngle, endAngle, anticlockwise) //画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。
这里使用的是canvas的路径绘制函数,但是每一次beginPath后因为只绘制了一个圆弧,所以可以看做画了一个实心圆形。
进行随机干扰线的生成
drawLines(context) { for (let i = 0; i < 2; i++) { context.strokeStyle = this.getRandomCol(0, 155); context.beginPath(); context.moveTo( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight) ); context.lineTo( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight) ); context.stroke(); } },
这里是通过for循环生成2条干扰线,使用moveTo和lineTo进行落笔、提笔操作。
完善组件加载出来就绘制验证码功能
在methods中添加drawPic()函数,将上述功能放在该函数中进行实现。
drawPic() { var canvas = document.getElementById("code-canvas"); var context = canvas.getContext("2d"); this.drawBackground(context); this.drawText(context); this.drawDot(context); this.drawLines(context); },
在mounted时执行drawPic()。
mounted() { this.drawPic(); },
在父界面引入该验证码组件
声明参数code作为生成随机验证码字符串的中介
data() { return { code: ref(""), }; },
在methods中添加生成随机验证码文本的函数
genCode() { this.code = ""; var str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for(let i = 0;i < 4;i++){ let index = Math.floor(Math.random()*62); this.code += str[index]; } },
在template中添加该组件并传入code,为组件添加点击事件实现点击刷新
<IdentifyCode :code="code" @click="genCode()"></IdentifyCode>
要实现页面加载出来就随机生成一个随机验证码字符串,我们还需要为生命周期mounted添加genCode()
mounted(){ this.genCode(); }
这个时候我们会发现,验证码中并没有文本生成,其原因是在mounted之后code才生成,而该组件的code早在这个阶段之前就传入并且在组件的mounted中进行了一次drawPic(),导致后续code更新时,该验证码不会发生变化,所以我们需要在组件中添加监听事件,监听code是否发生改变,若改变则再次执行drawPic()。
watch: { code() { this.drawPic(); }, },
组件代码一览
<template> <canvas id="code-canvas" :width="picWidth" :height="picHeight"></canvas> </template> <script> export default { name: "IdentifyCode", props: { code: { type: String, default: "2244", }, }, data() { return { picWidth: 120, picHeight: 35, }; }, methods: { getRandomVal(min, max) { return Math.floor(Math.random() * (max - min) + min); }, getRandomCol(min, max) { var r = this.getRandomVal(min, max); var g = this.getRandomVal(min, max); var b = this.getRandomVal(min, max); return "rgb(" + r + "," + g + "," + b + ")"; }, drawPic() { var canvas = document.getElementById("code-canvas"); var context = canvas.getContext("2d"); this.drawBackground(context); this.drawText(context); this.drawDot(context); this.drawLines(context); }, drawBackground(context) { context.fillStyle = this.getRandomCol(180, 240); context.fillRect(0, 0, this.picWidth, this.picHeight); }, drawText(context) { for (let i = 0; i < this.code.length; i++) { context.fillStyle = this.getRandomCol(0, 180); context.font = this.getRandomVal(20, 40) + "px serif"; let deg = this.getRandomVal(-20, 20); let x = 20 * (i + 1); let y = this.getRandomVal(25, 32); context.translate(x, y); context.rotate((Math.PI / 180) * deg); context.fillText(this.code[i], 0, 0); context.rotate(-(Math.PI / 180) * deg); context.translate(-x, -y); } }, drawDot(context) { for (let i = 0; i < 20; i++) { context.fillStyle = this.getRandomCol(0, 255); context.beginPath(); context.arc( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight), 1, 0, Math.PI * 2 ); context.fill(); } }, drawLines(context) { for (let i = 0; i < 2; i++) { context.strokeStyle = this.getRandomCol(0, 155); context.beginPath(); context.moveTo( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight) ); context.lineTo( this.getRandomVal(0, this.picWidth), this.getRandomVal(0, this.picHeight) ); context.stroke(); } }, }, watch: { code() { this.drawPic(); }, }, mounted() { this.drawPic(); }, }; </script>