上图为网易云盾的滑动拼图验证码, 其应该有一个专门的图片库, 裁剪的位置是固定的我的想法是, 随机生成图片, 随机生成位置, 再用 canvas 裁剪出滑块和背景图下面介绍具体步骤
首先随便找一张图片渲染到 canvas 上, 这里 #canvas 作为画布,#block 作为裁剪出来的小滑块
- <canvas width="310" height="155" id="canvas"></canvas>
- <canvas width="310" height="155" id="block"></canvas>
- var canvas = document.getElementById('canvas')
- var block = document.getElementById('block')
- var canvas_ctx = canvas.getContext('2d')
- var block_ctx = block.getContext('2d')
- var img = document.createElement('img')
- img.onload = function() {
- canvas_ctx.drawImage(img, 0, 0, 310, 155)
- block_ctx.drawImage(img, 0, 0, 310, 155)
- };
- img.src = 'img.jpg'
下面考虑如何裁剪出拼图的形状, 拼图形状比较复杂, 首先我们画一个正方形, 接着上边的代码写:
- var x = 150, y = 40, w = 42, r = 10, PI = Math.PI
- function draw(ctx) {
- ctx.beginPath()
- ctx.moveTo(x, y)
- ctx.lineTo(x + w, y)
- ctx.lineTo(x + w, y + w)
- ctx.lineTo(x, y + w)
- ctx.clip()
- }
- draw(canvas_ctx)
- draw(block_ctx)
x,y 为正方形左上角的坐标, 现在先随便写后边生成的时候用随机数, w 为正方形的边长, r 为后边画缺口的圆的半径我们先把绘图过程用函数封装起来, 方便后面同时操作背景和滑块用 clip() 方法裁剪图片后生成一个正方形
接下来画正方形上边和右边的圆形:
- function draw(ctx) {
- ctx.beginPath()
- ctx.moveTo(x,y)
- + ctx.lineTo(x+w/2,y)
- + ctx.arc(x+w/2,y-r+2, r,0,2*PI) //
- + ctx.lineTo(x+w/2,y)
- ctx.lineTo(x+w,y)
- + ctx.lineTo(x+w,y+w/2)
- + ctx.arc(x+w+r-2,y+w/2,r,0,2*PI) //
- + ctx.lineTo(x+w,y+w/2)
- ctx.lineTo(x+w,y+w)
- ctx.lineTo(x,y+w)
- ctx.lineTo(x,y)
- ctx.clip()
- }
两处注释的位置将圆心往内偏移了 2px, 实现缺口的样式然后是左边空心的部分, 由于 clip 是裁剪路径内的部分, 因此直接像上面画圆是不行的, 我们开启一条新的路径, 然后画圆将这个正方形遮盖出一个缺口, 这里会用到 globalCompositeOperation 属性,'xor'顾名思义代码接上边:
- function draw(ctx) {
- ctx.beginPath()
- ...
- ctx.lineTo(x,y)
- ctx.clip()
- + ctx.beginPath()
- + ctx.arc(x,y+w/2, r,1.5*PI,0.5*PI) // 只需要画正方形内的半圆就行, 方便背景图片的裁剪
- + ctx.globalCompositeOperation = "xor"
- + ctx.fill()
- }
现在一个基本的拼图形状有了, 我们调整 #block 的大小, 并将裁剪出来的滑块放入 #block 中:
- img.onload = function() {
- ctx.drawImage(img, 0, 0, 310, 155)
- block_ctx.drawImage(img, 0, 0, 310, 155)
- + var blockWidth = w + r * 2
- + var _y = y - r * 2 + 2 // 滑块实际的 y 坐标
- + var ImageData = block_ctx.getImageData(x, _y, blockWidth, blockWidth)
- + block.width = blockWidth
- + block_ctx.putImageData(ImageData, 0, _y)
- }
现在我们需要把左边画布展示原来的图片, 并且抠掉中间滑块的部分, 这里画路径的过程都是一样的, 唯一不同只是 clip() 那里改成 fill() 即可实现效果, 我们前面已经把画路径的过程封装成函数了, 稍作改动即可:
- - function draw(ctx) {
- + function draw(ctx, operation) {
- ...
- - ctx.clip()
- + ctx.fillStyle = '#fff'
- + ctx[operation]()
- ...
- }
- + draw(canvas_ctx, 'fill')
- + draw(block_ctx, 'clip')
接下来就是写样式了, 略过:
然后就是写拖动事件, 我们可以在鼠标按下时记录鼠标位置, 然后在拖动时给滑块设置 left 值最后在松开鼠标时, 判断滑块此时的 left 值和最开始裁剪滑块时的 x 值, 如果在一定范围内就算验证通过, 否则验证失败
最后再加上随机图片和随机剪切位置, 基本就 ok 了另外可以判断下鼠标移动时 y 轴的变化, 以判断是否是人在操作, 当然 web 安全这块神魔乱舞的, 我就不班门弄斧了, 只是做个简单判断就行
由于没有给切片边缘加上边框或者阴影, 导致某些图片的滑块可识别度不高, 需要后边完善下 (其实是我还没折腾出来 - -), 希望懂这块的大神帮我完善下 //
后边代码比较杂乱, 就不贴上来了, 查看完整代码点这里 演示地址点这里
来源: https://www.cnblogs.com/huanglei-/p/8568405.html