canvas是个非常强大的组件,网页上的验证码一般都是用服务器语言制作出来的
canvas同样是可以实现这个功能的
下面请观看效果图:
步骤呢其实也很简单
HTML部分:
1 <form action="" id="form" method="post" onsubmit="check()"> 2 <label for="chat">请输入验证码:</label> 3 <input type="text" id="chat"> 4 <canvas id="myCanvas" width="120" height="40"></canvas> 5 <button type="submit" id="btn">提交</button> 6 </form>
CSS部分:
1 form{ 2 width: 400px; 3 display: flex; 4 align-items: center; 5 justify-content: space-between; 6 } 7 #chat{ 8 width: 100px; 9 height: 30px; 10 outline: none; 11 font-size: 20px; 12 }
简单设置好表单的样式后,开始制作canvas
这是全部js代码
1 <script> 2 let c = document.getElementById('myCanvas'); 3 let ctx = c.getContext('2d'); 4 let chat = document.getElementById('chat'); 5 let form = document.getElementById('form'); 6 7 function fun(){ 8 let draw = { 9 bgColor : `rgb(${255},${255},${255})`, 10 dot : { 11 num : 20,//点的个数 12 radius : 1//点的半径 13 }, 14 line : { 15 num : 10,//线的个数 16 width : 1 //线的宽度 17 }, 18 code : { 19 num : 4,//字母个数 20 text : [],//存放字母 21 deg : [],//存放字母旋转角度 22 size : 25,//字体大小 23 maxWidth : 20//字体最大宽度 24 } 25 }; 26 27 //设置画布的背景颜色 28 ctx.fillStyle = draw.bgColor; 29 ctx.fillRect(0, 0, c.width, c.height); 30 31 let x1 = null; 32 let y1 = null; 33 let x2 = null; 34 let y2 = null; 35 let color = null; 36 let choose = null; 37 let code = null; 38 39 //小写字母的ascii码97~122,大写65~90 40 for( let i = 0; i < draw.code.num; i ++ ){ 41 choose = Math.floor(Math.random() * 2); 42 if(choose === 0){ 43 code = Math.floor(Math.random() * 26) + 97; 44 }else{ 45 code = Math.floor(Math.random() * 26) + 65; 46 } 47 draw.code.text.push(String.fromCharCode(code)); 48 draw.code.deg.push(Math.floor(Math.random() * 61) - 30);//倾斜角度从-30到30 49 } 50 51 for( let i = 0; i < draw.code.num; i ++ ){ 52 x1 = 10 + i * draw.code.size; 53 y1 = (c.height - draw.code.size) / 2 + draw.code.size; 54 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`; 55 56 //绘制字母 57 ctx.save(); 58 ctx.beginPath(); 59 ctx.fillStyle = color; 60 ctx.font = `${draw.code.size}px Microsoft YaHei`; 61 ctx.translate(x1 + draw.code.maxWidth / 2,y1 - draw.code.size / 2); 62 ctx.rotate(draw.code.deg[i] * Math.PI/180); 63 ctx.fillText(draw.code.text[i], - draw.code.maxWidth / 2, + draw.code.size / 2, draw.code.maxWidth); 64 ctx.restore(); 65 } 66 67 for( let i = 0; i < draw.dot.num; i ++ ){ 68 x1 = Math.random() * (c.width - draw.dot.radius) + draw.dot.radius;//随机点的初始横坐标 69 y1 = Math.random() * (c.height - draw.dot.radius) + draw.dot.radius;//随机点的初始纵坐标 70 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`;//随机点的颜色 71 72 //绘制点 73 ctx.beginPath(); 74 ctx.fillStyle = color; 75 ctx.arc( x1, y1, draw.dot.radius, 0, Math.PI * 2 ); 76 ctx.fill(); 77 } 78 79 for( let i = 0; i < draw.line.num; i ++ ){ 80 x1 = Math.random() * c.width;//线的开始横坐标 81 y1 = Math.random() * c.height;//线的开始纵坐标 82 x2 = Math.random() * c.width;//线的结束横坐标 83 y2 = Math.random() * c.height;//线的结束纵坐标 84 90 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`; 91 92 //绘制线 93 ctx.beginPath(); 94 ctx.lineWidth = draw.line.width; 95 ctx.strokeStyle = color; 96 ctx.moveTo(x1, y1); 97 ctx.lineTo(x2, y2); 98 ctx.stroke(); 99 } 100 return(draw.code.text);//返回数组用于验证 101 } 102 103 fun(); 104 105 let text = fun(); 106 107 function check(){ 108 form.action = chat.value === text.join('') ? 'https://www.baidu.com/' : '#'; 109 } 110 </script>
下面我一点点说:
1 let draw = { 2 bgColor : `rgb(${255},${255},${255})`, 3 dot : { 4 num : 20,//点的个数 5 radius : 1//点的半径 6 }, 7 line : { 8 num : 10,//线的个数 9 width : 1 //线的宽度 10 }, 11 code : { 12 num : 4,//字母个数 13 text : [],//存放字母 14 deg : [],//存放字母旋转角度 15 size : 25,//字体大小 16 maxWidth : 20//字体最大宽度 17 } 18 };
定义一个JSON数组来存放绘图所需的变量
1 ctx.fillStyle = draw.bgColor; 2 ctx.fillRect(0, 0, c.width, c.height);
设置画布背景颜色
1 let x1 = null; 2 let y1 = null; 3 let x2 = null; 4 let y2 = null; 5 let color = null; 6 let choose = null; 7 let code = null;
一些绘图所需的临时变量
我个人比较倾向于先绘制字母,再绘制点和线,这样干扰性更强
1 for( let i = 0; i < draw.code.num; i ++ ){ 2 choose = Math.floor(Math.random() * 2); 3 if(choose === 0){ 4 code = Math.floor(Math.random() * 26) + 97; 5 }else{ 6 code = Math.floor(Math.random() * 26) + 65; 7 } 8 draw.code.text.push(String.fromCharCode(code)); 9 draw.code.deg.push(Math.floor(Math.random() * 61) - 30);//倾斜角度从-30到30 10 }
首先绘制字母,字母有分大写和小写,这里我就用随机数来决定大写和小
小写字母的ascii码值97~122,大写65~90
将随机出的字母和随机出的(字母旋转角度)推入数组
1 for( let i = 0; i < draw.code.num; i ++ ){ 2 x1 = 10 + i * draw.code.size; 3 y1 = (c.height - draw.code.size) / 2 + draw.code.size; 4 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`; 5 6 //绘制字母 7 ctx.save(); 8 ctx.beginPath(); 9 ctx.fillStyle = color; 10 ctx.font = `${draw.code.size}px Microsoft YaHei`; 11 ctx.translate(x1 + draw.code.maxWidth / 2,y1 - draw.code.size / 2); 12 ctx.rotate(draw.code.deg[i] * Math.PI/180); 13 ctx.fillText(draw.code.text[i], - draw.code.maxWidth / 2, + draw.code.size / 2, draw.code.maxWidth); 14 ctx.restore(); 15 }
x1是字母绘制的起始横坐标,y1是起始纵坐标
注意:绘制字符和绘制图形是不一样的
字符的起始点位于字符的左下角,而图形的起始点位于图形的左上角
计算出起始坐标后,随机出字母的填充颜色
ctx.save();储存当前画笔信息,此时画笔的translate值为0,0,rotate值为0
字符的起始点是位于左下角的,而我们想要的旋转效果是绕着字符中心旋转,起始点就应该设置在字符中心
那么横坐标需要加上字母的1/2宽度,而纵坐标需要减去字母的高度(也就是字母的字体大小)
ctx.translate(x1 + draw.code.maxWidth / 2,y1 - draw.code.size / 2);
旋转字体,常规操作
ctx.rotate(draw.code.deg[i] * Math.PI/180);
这时候再减去刚刚变化的坐标值,回到原来的起始点
ctx.fillText(draw.code.text[i], - draw.code.maxWidth / 2, + draw.code.size / 2, draw.code.maxWidth);
ctx.restore();将画笔的信息重置回储存时的,也就是translate(0,0)、rotate(0),便于绘制下一个字母
1 for( let i = 0; i < draw.dot.num; i ++ ){ 2 x1 = Math.random() * (c.width - draw.dot.radius) + draw.dot.radius;//随机点的初始横坐标 3 y1 = Math.random() * (c.height - draw.dot.radius) + draw.dot.radius;//随机点的初始纵坐标 4 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`;//随机点的颜色 5 6 //绘制点 7 ctx.beginPath(); 8 ctx.fillStyle = color; 9 ctx.arc( x1, y1, draw.dot.radius, 0, Math.PI * 2 ); 10 ctx.fill(); 11 }
绘制点,随机出点的初始横坐标和纵坐标,仍然用x1,y1,因为字母绘制完已经不需要x1,y1了,无需再定义新的变量
1 for( let i = 0; i < draw.line.num; i ++ ){ 2 x1 = Math.random() * c.width;//线的开始横坐标 3 y1 = Math.random() * c.height;//线的开始纵坐标 4 x2 = Math.random() * c.width;//线的结束横坐标 5 y2 = Math.random() * c.height;//线的结束纵坐标12 color = `rgb(${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)},${Math.floor(Math.random() * 255)})`; 13 14 //绘制线 15 ctx.beginPath(); 16 ctx.lineWidth = draw.line.width; 17 ctx.strokeStyle = color; 18 ctx.moveTo(x1, y1); 19 ctx.lineTo(x2, y2); 20 ctx.stroke(); 21 }
最后绘制直线,仍然使用x1,y1做初始坐标,添加x2,y2做结束坐标
然后我们需要验证我们在输入框中输入的验证码是否正确
1 return(draw.code.text);
返回储存在数组中的字母
1 fun(); 2 let text = fun();
运行fun()函数,再令text = fun();
两个操作不能调换顺序,因为每次运行fun()得到的结果都是不一样的
1 function check(){ 2 form.action = chat.value === text.join('') ? 'https://www.baidu.com/' : '#'; 3 }
定义一个check函数来验证我们在输入框中输入的验证码是否正确
如果正确就跳到百度,不正确就刷新页面
但要注意的是前面html中的form标签要设置一个属性onsubmit="check()"
这样点击提交按钮的时候就会无视其他form设置的属性,先执行check函数,验证是否正确。
如果不设置onsubmit="check()",那么按照优先级顺序,会先执行form的action属性,会刷新页面,此时又会运行一次fun(),验证码就对不上了
这里只是做一个简单的验证码,包括字母 + 数字,字符变形,大小写均可这些效果我就不做了。