示例

背景

验证码主要是防止机器暴力破解。之前的验证码都是以静态为主,现在一些产品开始使用动态方式,增加破解的难度。动态方式以 gif 最为简单可靠。gif 兼容性好,尺寸小。这里分享的就是一种:用 JS 实现 gif 动态验证码的思路。感谢关注。

任务分解

  1. 绘制旋转的文字
  2. 计算每个字符出现位置和角度
  3. 生成 gif 图片

逐步求精

如何绘制旋转的文字?

了解能用的 API

  • context.rotate(angle) 使当前坐标系旋转 angle,单位弧度
  • context.translate(x, y) 使当前坐标系偏移 x, y,单位像素
  • context.font 设置字体
  • context.strokeText(text, x, y [, maxWidth ]) 给文本描边
  • context.fillText(text, x, y [, maxWidth ]) 给文本填充

怎么以文字的中心位置旋转?

void function() {// ...var x = 100;var y = 100;var angle = 1 / 8 * Math.PI;context.translate(x, y);context.rotate(angle);context.strokeText('A', 0, 0);// ...
}()

以文字的左下角为圆心旋转,不符合预期,见下图效果

本打算做一下偏移的计算,一想到要计算文本中心位置貌似还挺复杂。 还是看看其他人怎么做的,通过关键词 canvas rotate text center 找到一点线索。

context.save();
context.translate(newx, newy);
context.rotate(-Math.PI / 2);
context.textAlign = "center";
context.fillText("Your Label Here", labelXposition, 0);
context.restore();

textAlign 是横向对齐,再根据标准找到了一个纵向对齐 textBaseline

void function() {// ...context.textAlign = 'center'; // <<<<<<< insertcontext.textBaseline = 'middle'; // <<<<<<< insertvar x = 100;var y = 100;var angle = 1 / 8 * Math.PI;context.translate(x, y);context.rotate(angle);context.strokeText('A', 0, 0);// ...
}()

 

修改以后,效果符合预期,见下图:

按我的习惯就这种 “常用” 功能就封装成独立函数,方便以后使用。

/*** 绘制旋转的文字* @param {CanvasRenderingContext2D} context 上下文* @param {String} text 文本* @param {Number} x 中心坐标 x* @param {Number} y 中心坐标 y* @param {Number} angle 角度,单位弧度*/
function rotateText(context, text, x, y, angle) {if (!context) {return;}context.save(); // 保存上次的风格设置context.textAlign = 'center'; // 横向居中context.textBaseline = 'middle'; // 纵向居中context.translate(x, y); // 修改坐标系原点context.rotate(angle); // 旋转context.strokeText(text, 0, 0); // 绘制文本context.restore(); // 恢复上次的风格设置
}

如何计算每个字符出现位置和角度?

背景文字左右平移 + 旋转,生成随机的字符串计算中心坐标就好了

前景文字基本相似,只要上下来回移动和稍微摇摆,这里用的 cos 曲线控制摇摆。

如何生成 gif 图片

生成 gif 有第三方库可以使用 gifjs。 这里要注意的是,gifjs 用到 worker 技术,所以得在 http:// 环境里调试,不能用 file:// 环境

注意:由于添加的是同一个 canvas 对象,所以的是使用 copy 模式,将图像数据保留给每一帧。

gif.addFrame(canvasTemp, { delay: 100, copy: true });

完整代码

  • 线上演示
  • 代码地址
<!doctype html>
<html>
<head><meta charset="utf-8" /><style>
canvas {border: black 1px solid;
}</style><script src="../library/gif.js"></script>
</head>
<body><div>Key: <input type="text" maxlength="8" /> <input type="button" value="build" /></div><canvas width="300" height="70"></canvas><img width="300" height="70" /><a download="captcha.gif">download...</a><script>/*** 绘制旋转的文字* @param {CanvasRenderingContext2D} context 上下文* @param {String} text 文本* @param {Number} x 中心坐标 x* @param {Number} y 中心坐标 y* @param {Number} angle 角度,单位弧度*/
function rotateText(context, text, x, y, angle) {if (!context) {return;}context.save(); // 保存上次的风格设置context.textAlign = 'center'; // 横向居中context.textBaseline = 'middle'; // 纵向居中context.translate(x, y); // 修改坐标系原点context.rotate(angle); // 旋转context.strokeText(text, 0, 0); // 绘制文本context.restore(); // 恢复上次的风格设置
}/*** 随机字符串* @param{String} chars 字符串* @param{Number} len 长度*/
function randomText(chars, len) {var result = '';for (var i = 0; i < len; i++) {result += chars.charAt(parseInt(chars.length * Math.random()));}return result;
}void function() {// @see http://www.w3.org/TR/2dcontext/var canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '30px Verdana'; // 字体大小和字体名var lineHeight = 15; // 行高var backLength = 3;var backTexts = {};var backXOffsets = {};var keyYOffsets = {};var keyAOffsets = {};var backSpeed = 10000 + parseInt(100 * Math.random());var keySpeed = 12000 + parseInt(100 * Math.random());var key = '';function init(value) {key = String(value).toUpperCase();// 随机备件for (var i = 0; i < canvas.height / lineHeight; i++) {backTexts[i] = randomText('ABCDEFGHIJKLMNOPQRST0123456789', backLength);backXOffsets[i] = Math.random() * canvas.width;}for (var i = 0; i < key.length; i++) {keyYOffsets[i] = Math.random() * lineHeight / 2;keyAOffsets[i] = 0.05 - Math.random() * 0.1;}}function renderBack(now, context, text, y, xOffset) {var tick = now % backSpeed;for (var i = 0; i < backLength; i++) {var t = (xOffset + (tick / backSpeed) * canvas.width + (canvas.width / backLength) * i) % canvas.width;rotateText(context, text[i], t, y,i / backLength * Math.PI * 2 + (tick / backSpeed) * Math.PI * 2);}}function render(now, context) {context.fillStyle = '#FFFFFF';context.fillRect(0, 0, canvas.width, canvas.height);context.fillStyle = '#000000';// 绘制背景文字for (var i = 0; i < canvas.height / lineHeight; i++) {renderBack(now, context, backTexts[i], lineHeight * i, backXOffsets[i]);}// 绘制 keyvar tick = now % keySpeed;var keyCharWidth = canvas.width / key.length;for (var i = 0; i < key.length; i++) {var tx = keyCharWidth + (((canvas.width - keyCharWidth) / key.length) * i) % canvas.width;var ty = Math.cos(now / 1000) * Math.PI * keyYOffsets[i];rotateText(context, key[i], tx,canvas.height / 2 - ty, Math.cos(now / 1000) * Math.PI * 0.1 + keyAOffsets[i]);}}init('zswang');setInterval(function() {render(Number(new Date), context);}, 100);document.querySelector('input[type=text]').addEventListener('input', function() {init(this.value);});document.querySelector('input[type=button]').addEventListener('click', function() {var self = this;self.disabled = true;var gif = new GIF({repeat: 0,workers: 2,quality: 10,workerScript: '../library/gif.worker.js'});// 生成 gif 图片var canvasTemp = document.createElement('canvas');canvasTemp.width = canvas.width;canvasTemp.height = canvas.height;var context = canvasTemp.getContext('2d');context.font = '30px Verdana'; // 字体大小和字体名context.textAlign = 'center';for (var i = 0; i < 5000; i += 100) {render(i, context);gif.addFrame(canvasTemp, { delay: 100, copy: true });}gif.on('finished', function(blob) {var url = URL.createObjectURL(blob);document.querySelector('img').src = url;document.querySelector('a').href = url;self.disabled = false;});gif.render();});
}();</script>
</body>
</html>

后记

功能比较简单,也写得比较简单,仅供参考。如果要应用到实战,还有很多细节要考虑

  • gif 创建的过程必然得放到后端完成,否则 兼容性、性能、安全性 都是问题(这块和传统的验证过程并无区别)。
  • 缓存(背景效果可以重复利用一段时间)。
  • 图片大小需要优化,目前是 200K(通过调整帧率和压缩比)。
  • 提供方便的调用接口(模块化)。

参考资料

  • HTML Canvas 2D Context
  • JavaScript GIF encoding library
  • Drawing rotated text on a HTML5 canvas

作者:zswang (http://weibo.com/zswang) - 站在巨人的肩上也要成为巨人的一部分

实现动态验证码的思路相关推荐

  1. Linux下使用ssh动态验证码登陆机器

    ssh动态验证码登录机器 Google Authenticator是一个动态验证码程序,兼容各种智能手机平板设备,可以用来做各种帐号的二次验证,增加帐号的安全性.SSH是Linux系统的最重要防线之一 ...

  2. Linux下使用Google Authenticator配置SSH登录动态验证码

    说明: 1.一般ssh登录服务器,只需要输入账号和密码. 2.本教程的目的:在账号和密码之间再增加一个 验证码,只有输入正确的验证码之后,再输入 密码才能登录.这样就增强了ssh登录的安全性. 3.账 ...

  3. java校验码的设计_Java动态验证码单线设计的两种方法

    1.java的动态验证码我这里将介绍两种方法: 一:根据java本身提供的一种验证码的写法,这种呢只限于大家了解就可以了,因为java自带的模式编写的在实际开发中是没有意义的,所以只供学习一下就可以了 ...

  4. phpgif图片包_php生成动态验证码gif图片

    这是一个通过php生成的动态验证码图片的示例,重点是可以运行哦!下面先发下效果图: 下面是php生成动态验证码需要用到的相关类和函数. /** *ImageCode 生成包含验证码的GIF图片的函数 ...

  5. .NET中生成动态验证码

    NET中生成动态验证码 验证码是图片上写上几个字,然后对这几个字做特殊处理,如扭曲.旋转.修改文字位置,然后加入一些线条,或加入一些特殊效果,使这些在人类能正常识别的同时,机器却很难识别出来,以达到防 ...

  6. 微信小程序-动态验证码

    微信小程序-动态验证码 一.创建自定义组件verification-code 二.在index页面使用 一.创建自定义组件verification-code verification-code.js ...

  7. 如何用代码实现手机接收动态验证码

    如何用代码实现手机接收动态验证码 我们现在注册一个app应用经常会使用手机验证码,那Java程序怎么实现的呢? 首先我们要明白,手机接收验证码是基于三大运营商的服务实现的,而阿里云服务为我们实现了免去 ...

  8. java验证码验证码_Java登录页面实时验证用户名密码和动态验证码

    ●登录名和密码是同时验证的,并不是先验证登录名是否存在,然后再验证密码是否正确,是同时进行验证,若登录名和密码当中一个条件不符合,则提示用户登录名或者密码错误, 这样做的意义是为了保证用户信息的安全( ...

  9. Java登录页面实时验证用户名密码和动态验证码

    ●登录名和密码是同时验证的,并不是先验证登录名是否存在,然后再验证密码是否正确,是同时进行验证,若登录名和密码当中一个条件不符合,则提示用户登录名或者密码错误, 这样做的意义是为了保证用户信息的安全( ...

最新文章

  1. 爬取网站图片并保存到本地
  2. 阶乘和matlab实现
  3. 在 Azure Functions 上使用不同的路由前缀
  4. WCF简单教程(6) 单向与双向通讯
  5. fabric shim安装合约_智能合约简介_智能合约开发_Hyperledger Fabric_开发指南_区块链服务 BaaS - 阿里云...
  6. C++ 自定义string类 重载相关运算符
  7. 团队第五次 # scrum meeting
  8. 关情纸尾-----Quartz2D-绘制富文本,绘制图片.
  9. MRC522(1):卡片ID号的读写
  10. 高清壁纸|是时候换换心情了
  11. 深度学习在Airbnb搜索推荐中的应用实践
  12. 工具库用久了,你还会原生操作 Cookie 吗?
  13. 学会这些知识普通人也能财务自由
  14. 修改echarts 3D柱状图柱子大小(粗细)的方法
  15. 图像坐标球面投影_晶体的球面坐标与球面投影
  16. Luckily general gradient for spherical harmonics is defined
  17. 计算机维修工实操,计算机维修工(三级)操作技能练习题.pdf
  18. 《程序人生》系列-一个月了,我要谢谢,你、你、还有你
  19. Web 窗体控件简介
  20. centos linux开机启动项,Centos 配置开机启动项

热门文章

  1. 鱼塘钓鱼(信息学奥赛一本通-T1373)
  2. 最长公共子序列(信息学奥赛一本通-T1265)
  3. 数字反转(信息学奥赛一本通-T1089)
  4. linux安装c++版本eclipse以及编译增加指定库
  5. 我就传个图片都不通过迈
  6. 基于JavaScript的在线语音识别库Julius
  7. mui 实现a锚点定位 (demo演示)【建议:仅作为参考】
  8. java request 处理过程_小猿圈Java开发之从代码看spring mvc请求处理过程
  9. for循环中gets_Python中for循环的一些非常规操作
  10. thinkphp mysql desc table_数据库表结构_ThinkPHP 数据库表结构处理类(简单实用)-云栖社区-阿里云...