写本文的初衷是纯粹为cavans练手,实际工作当中并不建议使用前端生成的图片验证码;前端使用验证码的初衷就是为了防止用户错误请求接口,也为了规避别有用心之人频繁请求接口对服务器进行攻击;因此如果后端能提供验证码接口则尽量使用后端提供的接口
场景分析
随着时代的进步我们可能已经很久没遇到过图片验证码了,现在主流的验证方式基本上为短信,邮箱,以及拖动验证;但是并不妨碍我们学习它,实现图片验证码的主要方式是cavans,我们可以借此机会学习一下cavans;
需求分析
你还记得上一次见到图片验证码是什么时候吗?你还记得一个图片验证码是由哪些元素组成的吗?
我总结下来就只有两点:
- 字符串
- 干扰元素
而字符串与干扰元素也拥有多重属性比如:
- 相对偏移量
- 字体
- 字体颜色
- 字体大小
- 阴影效果
- 字符串长度
而字符串与干扰元素是由图片承载的,图片也拥有宽高以及背景色
而对于一个完整的需求而言还需要有一些交互比如:
- 图片的生成
- 图片的刷新
- 验证码的比对
实现
接下来我们进行简单的实现,通过上面的分析我们大致知道了我们需要属性因此接下来我们可以创建
class Verification{
constructor (length=6,style={width:200,height:30,hasDot:true,hasLine:true}) {
this.code = null//随机code字符串
this.path = null//生成的图片地址
this.length = length//验证码长度
this.width = style.width//图片宽度
this.height = style.height//图片高度
this.hasDot = style.hasDot//是否有干扰点
this.hasLine = style.hasLine//是否有干扰线
this.create()
}
create(){
//创建随机字符串
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
const maxLength = chars.length
let code = ''
for (let i = 0; i < this.length; i++) {
code = code + chars.charAt(Math.floor(Math.random() * maxLength))
}
this.code = code
//构建canvas
this.createPath()
}
//构建canvas
createPath(){
//获取随机数
const getRandomNum=(min, max)=> {
return Math.floor(Math.random() * (max - min) + min);
}
//获取随机色
const getRandomColor=(min, max,a=1)=> {
let r = getRandomNum(min, max);
let g = getRandomNum(min, max);
let b = getRandomNum(min, max);
return `rgba(${r},${g},${b},${a})`;
}
const canvas = document.createElement('canvas')
canvas.width = this.width
canvas.height = this.height
const ctx = canvas.getContext('2d')
ctx.textBaseline = "middle";//设置对其方式
ctx.fillStyle = getRandomColor(180, 240);//图片背景色设置,尽量将背景设置成浅色,文字为深色方便阅读
ctx.fillRect(0, 0, this.width, this.height);//背景色绘制
[...this.code].forEach((char,i)=>{
ctx.font = getRandomNum(this.height/2, this.height) + 'px pingfang sc'; // 随机生成字体大小
ctx.fillStyle = getRandomColor(80, 150) // 随机生成字体颜色
ctx.shadowOffsetX = getRandomNum(-5, 5);//文字阴影X轴偏移量
ctx.shadowOffsetY = getRandomNum(-5, 5);//文字阴影Y轴偏移量
ctx.shadowColor = getRandomColor(80, 150,0.4);//文字阴影颜色
//文字渲染X偏移量计算
/*
* 将图片的width除以code的长度获取文字在图片中的偏移,避免重叠
* */
let x = this.width / this.length * i;
//因为文字大小是大于等于this.height / 2而小于等于this.height 所以设置this.height / 2可展示全部
let y = this.height / 2;
//文字旋转偏移量
let deg = getRandomNum(-10, 10);
//设置文字偏移
ctx.translate(x, y);
//设置文字旋转
ctx.rotate(deg * Math.PI / 180);
//文字绘制
ctx.fillText(char, 0, 0);
//画布旋转回正
ctx.rotate(-deg * Math.PI / 180);
//画布偏移回正
ctx.translate(-x, -y);
//是否有线
if (this.hasLine) {
ctx.strokeStyle = getRandomColor(60, 160);//随机设置线颜色
ctx.beginPath();//开始路径
ctx.moveTo(getRandomNum(0, this.width), getRandomNum(0, this.height));//线起点
ctx.lineTo(getRandomNum(0, this.width), getRandomNum(0, this.height));//线终点
ctx.stroke();//线绘制
}
//是否有点
if(this.hasDot){
ctx.fillStyle = getRandomColor(0, 255);//随机设置点颜色
ctx.beginPath();//开始路径
ctx.arc(getRandomNum(0, this.width), getRandomNum(0, this.height), 1, 0, 2 * Math.PI);//点设置
ctx.fill();//点绘制
}
})
this.path = canvas.toDataURL("image/png")
}
//验证输入的验证码是否与生成的相等
equal(code){
//均转换为英文大写进行比对
return code.toUpperCase()===this.code.toUpperCase()
}
}
const v = new Verification()
document.getElementById('picture').setAttribute("src",v.path)