本文主要介绍用canvas实现图形验证码的一些思路以及如何用javascript面向对象的方式更友好的实现canvas的功能,关于canvas的一些基本使用方法和API我整理了一个思维导图,大家感兴趣的可以参考学习。

你将收获
- 闭包的使用
- canvas常用api的使用
- javascript面向对象的实现方式
- 实现一个canvas的图形验证码的一般思路和常用算法
设计思路
- 用canvas生成画布
- 用canvas画干扰线或躁点
- 生成随机不重复的n的字母
- 用canvas绘制文字
- 初始化和canvas点击事件
- 组件化封装
文末将附上组件封装的源码,欢迎大家随时沟通交流。关于项目的打包,我将使用自己基于gulp4搭建的9012教你如何使用gulp4开发项目脚手架。
效果预览

实现思路
我将按照上文中的设计思路的步骤一步步实现,首先我们先定义一个es5类:
1function Gcode(el, option) { 2 this.el = typeof el === 'string' ? document.querySelector(el) : el; 3 this.option = option; 4 this.init(); 5}
其中init是用来初始化用的,参数el代表需要挂载的元素或元素id,option为传入的可选项,稍后会在代码中体现,通常这也是面向对象的常用套路。
- 绘制画布
1Gcode.prototype = { 2 constructor: Gcode, 3 init: function() { 4 if(this.el.getContext) { 5 isSupportCanvas = true; 6 var ctx = this.el.getContext('2d'), 7 // 设置画布宽高 8 cw = this.el.width = this.option.width || 200, 9 ch = this.el.height = this.option.height || 40; 10 } 11 } 12}
这里我们在初始化方法中先定义一个canvas画布,宽高为用户自定义的宽高,默认为200*40。
- 绘制干扰线
1// 画干扰线 2drawLine: function(ctx, lineNum, maxW, maxH) { 3 ctx.clearRect(0, 0, maxW, maxH); 4 for(var i=0; i < lineNum; i++) { 5 var dx1 = Math.random()* maxW, 6 dy1 = Math.random()* maxH, 7 dx2 = Math.random()* maxW, 8 dy2 = Math.random()* maxH; 9 ctx.strokeStyle = 'rgb(' + 255*Math.random() + ',' + 255*Math.random() + ',' + 255*Math.random() + ')'; 10 ctx.beginPath(); 11 ctx.moveTo(dx1, dy1); 12 ctx.lineTo(dx2, dy2); 13 ctx.stroke(); 14 } 15}
这里我们对类Gcode定义原型方法drawLine,然后通过for循环绘制随机位置的线条,为了让canvas每次点击能清空之前的干扰线,我们使用clearRect来清除画布。
- 生成随机不重复的n个字符
我们通过递归实现,如下==:
1// 生成唯一文字 2generateUniqueText: function(source, hasList, limit) { 3 var text = source[Math.floor(Math.random()*limit)]; 4 if(hasList.indexOf(text) > -1) { 5 return this.generateUniqueText(source, hasList, limit) 6 }else { 7 return text 8 } 9} 10// 生成指定个数的随机文字 11randomText: function(len) { 12 var source = ['a', 'b', 'c', 'd', 'e', 13 'f', 'g', 'h', 'i', 'j', 14 'k', 'l', 'm', 'o', 'p', 15 'q', 'r', 's', 't', 'u', 16 'v', 'w', 'x', 'y', 'z']; 17 var result = []; 18 var sourceLen = source.length; 19 for(var i=0; i< len; i++) { 20 var text = this.generateUniqueText(source, result, sourceLen); 21 result.push(text) 22 } 23 return result.join('') 24}
我们通过定义一个字母表,传入生成的随机字母的个数,配合generateUniqueText来实现生成唯一不重复的n个随机字符。当然笔者认为这个方法并不优雅,你也可以使用uuid的方式或者更好的方式,欢迎随时和笔者交流。
- 用canvas绘制文字
1// 画文字 2drawText: function(ctx, text, maxH) { 3 var len = text.length; 4 for(var i=0; i < len; i++) { 5 var dx = 30 * Math.random() + 30* i, 6 dy = Math.random()* 5 + maxH/2; 7 ctx.fillStyle = 'rgb(' + 255*Math.random() + ',' + 255*Math.random() + ',' + 255*Math.random() + ')'; 8 ctx.font = '30px Helvetica'; 9 ctx.textBaseline = 'middle'; 10 ctx.fillText(text[i], dx, dy); 11 } 12},
这里和上文画线实现类似。就不做过多介绍了。
- 初始化和canvas点击事件
接下来我们看看完整的初始化代码:
1init: function() { 2 if(this.el.getContext) { 3 isSupportCanvas = true; 4 var ctx = this.el.getContext('2d'), 5 // 设置画布宽高 6 cw = this.el.width = this.option.width || 200, 7 ch = this.el.height = this.option.height || 40, 8 textLen = this.option.textLen || 4, 9 lineNum = this.option.lineNum || 4; 10 var text = this.randomText(textLen); 11 12 this.onClick(ctx, textLen, lineNum, cw, ch); 13 this.drawLine(ctx, lineNum, cw, ch); 14 this.drawText(ctx, text, ch); 15 } 16}
点击事件主要是为了用户点击可以切换验证码:
1onClick: function(ctx, textLen, lineNum, cw, ch) { 2 var _ = this; 3 this.el.addEventListener('click', function(){ 4 text = _.randomText(textLen); 5 _.drawLine(ctx, lineNum, cw, ch); 6 _.drawText(ctx, text, ch); 7 }, false) 8}
到此,一个完整的验证码组件实现完成,怎么用呢?如下:
1new Gcode('#canvas_code', { 2 lineNum: 6, // 可选 3 textLen: 4, // 可选 4 width: 200, // 可选 5 height: 50 // 可选 6 })
完整代码如下,欢迎学习交流:
1// canvas绘制图形验证码 2 (function(){ 3 function Gcode(el, option) { 4 this.el = typeof el === 'string' ? document.querySelector(el) : el; 5 this.option = option; 6 this.init(); 7 } 8 Gcode.prototype = { 9 constructor: Gcode, 10 init: function() { 11 if(this.el.getContext) { 12 isSupportCanvas = true; 13 var ctx = this.el.getContext('2d'), 14 // 设置画布宽高 15 cw = this.el.width = this.option.width || 200, 16 ch = this.el.height = this.option.height || 40, 17 textLen = this.option.textLen || 4, 18 lineNum = this.option.lineNum || 4; 19 var text = this.randomText(textLen); 20 21 this.onClick(ctx, textLen, lineNum, cw, ch); 22 this.drawLine(ctx, lineNum, cw, ch); 23 this.drawText(ctx, text, ch); 24 } 25 }, 26 onClick: function(ctx, textLen, lineNum, cw, ch) { 27 var _ = this; 28 this.el.addEventListener('click', function(){ 29 text = _.randomText(textLen); 30 _.drawLine(ctx, lineNum, cw, ch); 31 _.drawText(ctx, text, ch); 32 }, false) 33 }, 34 // 画干扰线 35 drawLine: function(ctx, lineNum, maxW, maxH) { 36 ctx.clearRect(0, 0, maxW, maxH); 37 for(var i=0; i < lineNum; i++) { 38 var dx1 = Math.random()* maxW, 39 dy1 = Math.random()* maxH, 40 dx2 = Math.random()* maxW, 41 dy2 = Math.random()* maxH; 42 ctx.strokeStyle = 'rgb(' + 255*Math.random() + ',' + 255*Math.random() + ',' + 255*Math.random() + ')'; 43 ctx.beginPath(); 44 ctx.moveTo(dx1, dy1); 45 ctx.lineTo(dx2, dy2); 46 ctx.stroke(); 47 } 48 }, 49 // 画文字 50 drawText: function(ctx, text, maxH) { 51 var len = text.length; 52 for(var i=0; i < len; i++) { 53 var dx = 30 * Math.random() + 30* i, 54 dy = Math.random()* 5 + maxH/2; 55 ctx.fillStyle = 'rgb(' + 255*Math.random() + ',' + 255*Math.random() + ',' + 255*Math.random() + ')'; 56 ctx.font = '30px Helvetica'; 57 ctx.textBaseline = 'middle'; 58 ctx.fillText(text[i], dx, dy); 59 } 60 }, 61 // 生成指定个数的随机文字 62 randomText: function(len) { 63 var source = ['a', 'b', 'c', 'd', 'e', 64 'f', 'g', 'h', 'i', 'j', 65 'k', 'l', 'm', 'o', 'p', 66 'q', 'r', 's', 't', 'u', 67 'v', 'w', 'x', 'y', 'z']; 68 var result = []; 69 var sourceLen = source.length; 70 for(var i=0; i< len; i++) { 71 var text = this.generateUniqueText(source, result, sourceLen); 72 result.push(text) 73 } 74 return result.join('') 75 }, 76 // 生成唯一文字 77 generateUniqueText: function(source, hasList, limit) { 78 var text = source[Math.floor(Math.random()*limit)]; 79 if(hasList.indexOf(text) > -1) { 80 return this.generateUniqueText(source, hasList, limit) 81 }else { 82 return text 83 } 84 } 85 } 86 new Gcode('#canvas_code', { 87 lineNum: 6 88 }) 89 })();
如果想体验实际案例效果和技术交流,或者感受更多原创canvas,h5游戏demo,可以关注趣谈前端感受哈
更多推荐
- 用 JavaScript 和 C3 实现一个转盘小游戏
- 教你用200行代码写一个爱豆拼拼乐H5小游戏(附源码)
- 基于react/vue生态的前端集成解决方案探索与总结
- 9012教你如何使用gulp4开发项目脚手架
- 如何用不到200行代码写一款属于自己的js类库)
- 让你瞬间提高工作效率的常用js函数汇总(持续更新)
- 一张图教你快速玩转vue-cli3
- 3分钟教你用原生js实现具有进度监听的文件上传预览组件
- 3分钟教你用原生js实现具有进度监听的文件上传预览组件
- 使用Angular8和百度地图api开发《旅游清单》
- js基本搜索算法实现与170万条数据下的性能测试
- 《前端算法系列》如何让前端代码速度提高60倍
- 《前端算法系列》数组去重
- vue高级进阶系列——用typescript玩转vue和vuex
- 前端三年,谈谈最值得读的5本书籍
