JS实现滑动拼图验证功能完整示例


Posted in Javascript onMarch 29, 2020

本文实例讲述了JS实现滑动拼图验证功能。分享给大家供大家参考,具体如下:

先看一下效果图:

JS实现滑动拼图验证功能完整示例

JS实现滑动拼图验证功能完整示例

JS实现滑动拼图验证功能完整示例

设置画布滑块属性

const l = 42, // 滑块边长
 r = 10, // 滑块半径
 w = 310, // canvas宽度
 h = 155, // canvas高度
 PI = Math.PI
const L = l + r * 2 // 滑块实际边长

设置背景图片:

图片链接地址可以自行更换

function getRandomImg() {
 return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 100)
}

CSS部分代码:

.container {
 width: 310px;
 margin: 100px auto;
}
#msg {
 width: 100%;
 line-height: 40px;
 font-size: 14px;
 text-align: center;
}
a:link,
a:visited,
a:hover,
a:active {
 margin-left: 100px;
 color: #0366D6;
}
.block {
 position: absolute;
 left: 0;
 top: 0;
}
.sliderContainer {
 position: relative;
 text-align: center;
 width: 310px;
 height: 40px;
 line-height: 40px;
 margin-top: 15px;
 background: #f7f9fa;
 color: #45494c;
 border: 1px solid #e4e7eb;
}
.sliderContainer_active .slider {
 height: 38px;
 top: -1px;
 border: 1px solid #1991FA;
}
.sliderContainer_active .sliderMask {
 height: 38px;
 border-width: 1px;
}
.sliderContainer_success .slider {
 height: 38px;
 top: -1px;
 border: 1px solid #52CCBA;
 background-color: #52CCBA !important;
}
.sliderContainer_success .sliderMask {
 height: 38px;
 border: 1px solid #52CCBA;
 background-color: #D2F4EF;
}
.sliderContainer_success .sliderIcon {
 background-position: 0 0 !important;
}
.sliderContainer_fail .slider {
 height: 38px;
 top: -1px;
 border: 1px solid #f57a7a;
 background-color: #f57a7a !important;
}
.sliderContainer_fail .sliderMask {
 height: 38px;
 border: 1px solid #f57a7a;
 background-color: #fce1e1;
}
.sliderContainer_fail .sliderIcon {
 background-position: 0 -83px !important;
}
.sliderContainer_active .sliderText,
.sliderContainer_success .sliderText,
.sliderContainer_fail .sliderText {
 display: none;
}
.sliderMask {
 position: absolute;
 left: 0;
 top: 0;
 height: 40px;
 border: 0 solid #1991FA;
 background: #D1E9FE;
}
.slider {
 position: absolute;
 top: 0;
 left: 0;
 width: 40px;
 height: 40px;
 background: #fff;
 box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
 cursor: pointer;
 transition: background .2s linear;
}
.slider:hover {
 background: #1991FA;
}
.slider:hover .sliderIcon {
 background-position: 0 -13px;
}
.sliderIcon {
 position: absolute;
 top: 15px;
 left: 13px;
 width: 14px;
 height: 10px;
 background: url(img/tb.png) 0 -26px;
 background-size: 34px 471px;
}
.refreshIcon {
 position: absolute;
 right: 0;
 top: 0;
 width: 34px;
 height: 34px;
 cursor: pointer;
 background: url(img/tb.png) 0 -437px;
 background-size: 34px 471px;
}

页面,页面只用放一个div就可以了

<div class="container">
 <div id="captcha" style="position: relative"></div>
</div>

js部分代码,包括验证是否正确

<script type="text/javascript">
 (function(window) {
 const l = 42, // 滑块边长
  r = 10, // 滑块半径
  w = 310, // canvas宽度
  h = 155, // canvas高度
  PI = Math.PI
 const L = l + r * 2 // 滑块实际边长
 function getRandomNumberByRange(start, end) {
  return Math.round(Math.random() * (end - start) + start)
 }
 function createCanvas(width, height) {
  const canvas = createElement('canvas')
  canvas.width = width
  canvas.height = height
  return canvas
 }
 function createImg(onload) {
  const img = createElement('img')
  img.crossOrigin = "Anonymous"
  img.onload = onload
  img.onerror = () => {
  img.src = getRandomImg()
  }
  img.src = getRandomImg()
  return img
 }
 function createElement(tagName) {
  return document.createElement(tagName)
 }
 function addClass(tag, className) {
  tag.classList.add(className)
 }
 function removeClass(tag, className) {
  tag.classList.remove(className)
 }
 function getRandomImg() {
  return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 100)
 }
 function draw(ctx, operation, x, y) {
  ctx.beginPath()
  ctx.moveTo(x, y)
  ctx.lineTo(x + l / 2, y)
  ctx.arc(x + l / 2, y - r + 2, r, 0, 2 * PI)
  ctx.lineTo(x + l / 2, y)
  ctx.lineTo(x + l, y)
  ctx.lineTo(x + l, y + l / 2)
  ctx.arc(x + l + r - 2, y + l / 2, r, 0, 2 * PI)
  ctx.lineTo(x + l, y + l / 2)
  ctx.lineTo(x + l, y + l)
  ctx.lineTo(x, y + l)
  ctx.lineTo(x, y)
  ctx.fillStyle = '#fff'
  ctx[operation]()
  ctx.beginPath()
  ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI)
  ctx.globalCompositeOperation = "xor"
  ctx.fill()
 }
 function sum(x, y) {
  return x + y
 }
 function square(x) {
  return x * x
 }
 class jigsaw {
  constructor(el, success, fail) {
  this.el = el
  this.success = success
  this.fail = fail
  }
  init() {
  this.initDOM()
  this.initImg()
  this.draw()
  this.bindEvents()
  }
  initDOM() {
  const canvas = createCanvas(w, h) // 画布
  const block = canvas.cloneNode(true) // 滑块
  const sliderContainer = createElement('div')
  const refreshIcon = createElement('div')
  const sliderMask = createElement('div')
  const slider = createElement('div')
  const sliderIcon = createElement('span')
  const text = createElement('span')
  block.className = 'block'
  sliderContainer.className = 'sliderContainer'
  refreshIcon.className = 'refreshIcon'
  sliderMask.className = 'sliderMask'
  slider.className = 'slider'
  sliderIcon.className = 'sliderIcon'
  text.innerHTML = '向右滑动滑块填充拼图'
  text.className = 'sliderText'
  const el = this.el
  el.appendChild(canvas)
  el.appendChild(refreshIcon)
  el.appendChild(block)
  slider.appendChild(sliderIcon)
  sliderMask.appendChild(slider)
  sliderContainer.appendChild(sliderMask)
  sliderContainer.appendChild(text)
  el.appendChild(sliderContainer)
  Object.assign(this, {
   canvas,
   block,
   sliderContainer,
   refreshIcon,
   slider,
   sliderMask,
   sliderIcon,
   text,
   canvasCtx: canvas.getContext('2d'),
   blockCtx: block.getContext('2d')
  })
  }
  initImg() {
  const img = createImg(() => {
   this.canvasCtx.drawImage(img, 0, 0, w, h)
   this.blockCtx.drawImage(img, 0, 0, w, h)
   const y = this.y - r * 2 + 2
   const ImageData = this.blockCtx.getImageData(this.x, y, L, L)
   this.block.width = L
   this.blockCtx.putImageData(ImageData, 0, y)
  })
  this.img = img
  }
  draw() {
  // 随机创建滑块的位置
  this.x = getRandomNumberByRange(L + 10, w - (L + 10))
  this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10))
  draw(this.canvasCtx, 'fill', this.x, this.y)
  draw(this.blockCtx, 'clip', this.x, this.y)
  }
  clean() {
  this.canvasCtx.clearRect(0, 0, w, h)
  this.blockCtx.clearRect(0, 0, w, h)
  this.block.width = w
  }
  bindEvents() {
  this.el.onselectstart = () => false
  this.refreshIcon.onclick = () => {
   this.reset()
  }
  let originX, originY, trail = [],
   isMouseDown = false
  this.slider.addEventListener('mousedown', function(e) {
   originX = e.x, originY = e.y
   isMouseDown = true
  })
  document.addEventListener('mousemove', (e) => {
   if(!isMouseDown) return false
   const moveX = e.x - originX
   const moveY = e.y - originY
   if(moveX < 0 || moveX + 38 >= w) return false
   this.slider.style.left = moveX + 'px'
   var blockLeft = (w - 40 - 20) / (w - 40) * moveX
   this.block.style.left = blockLeft + 'px'
   addClass(this.sliderContainer, 'sliderContainer_active')
   this.sliderMask.style.width = moveX + 'px'
   trail.push(moveY)
  })
  document.addEventListener('mouseup', (e) => {
   if(!isMouseDown) return false
   isMouseDown = false
   if(e.x == originX) return false
   removeClass(this.sliderContainer, 'sliderContainer_active')
   this.trail = trail
   const {
   spliced,
   TuringTest
   } = this.verify()
   if(spliced) {
   if(TuringTest) {
    addClass(this.sliderContainer, 'sliderContainer_success')
    this.success && this.success()
   } else {
    addClass(this.sliderContainer, 'sliderContainer_fail')
    this.text.innerHTML = '再试一次'
    this.reset()
   }
   } else {
   alert("验证失败");
   addClass(this.sliderContainer, 'sliderContainer_fail')
   this.fail && this.fail();
                        //验证失败后,1秒后重新加载图片
   setTimeout(() => {
    this.reset()
   }, 1000)
   }
  })
  }
  verify() {
  const arr = this.trail // 拖动时y轴的移动距离
  const average = arr.reduce(sum) / arr.length // 平均值
  const deviations = arr.map(x => x - average) // 偏差数组
  const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) // 标准差
  const left = parseInt(this.block.style.left)
  return {
   spliced: Math.abs(left - this.x) < 10,
   TuringTest: average !== stddev, // 只是简单的验证拖动轨迹,相等时一般为0,表示可能非人为操作
  }
  }
  reset() {
  this.sliderContainer.className = 'sliderContainer'
  this.slider.style.left = 0
  this.block.style.left = 0
  this.sliderMask.style.width = 0
  this.clean()
  this.img.src = getRandomImg()
  this.draw()
  }
 }
 window.jigsaw = {
  init: function(element, success, fail) {
  new jigsaw(element, success, fail).init()
  }
 }
 }(window))
 jigsaw.init(document.getElementById('captcha'), function() {
 alert("验证成功");
 })
</script>

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
javascript验证身份证完全方法具体实现
Nov 18 Javascript
php显示当前文件所在的文件以及文件夹所有文件以树形展开
Dec 13 Javascript
详解JavaScript中的4种类型识别方法
Sep 14 Javascript
AngularJs中route的使用方法和配置
Feb 04 Javascript
JS创建对象的写法示例
Nov 04 Javascript
JS根据生日月份和日期计算星座的简单实现方法
Nov 24 Javascript
微信小程序 滚动到某个位置添加class效果实现代码
Apr 19 Javascript
Linux系统中利用node.js提取Word(doc/docx)及PDF文本的内容
Jun 17 Javascript
详解Angular2表单-模板驱动的表单(Template-Driven Forms)
Aug 04 Javascript
微信小程序视图控件与bindtap之间的问题的解决
Apr 08 Javascript
Vue路由权限控制解析
Nov 09 Javascript
vue-cli中实现响应式布局的方法
Mar 02 Vue.js
json_decode 索引为数字时自动排序问题解决方法
Mar 28 #Javascript
JS中FormData类实现文件上传
Mar 27 #Javascript
JS中FileReader类实现文件上传及时预览功能
Mar 27 #Javascript
js、jquery实现列表模糊搜索功能过程解析
Mar 27 #jQuery
开发Node CLI构建微信小程序脚手架的示例
Mar 27 #Javascript
微信小程序间使用navigator跳转传值问题实例分析
Mar 27 #Javascript
vue跳转页面的几种方法(推荐)
Mar 26 #Javascript
You might like
第十四节 命名空间 [14]
2006/10/09 PHP
php实现的click captcha点击验证码类实例
2014/09/23 PHP
strpos() 函数判断字符串中是否包含某字符串的方法
2019/01/16 PHP
JavaScript入门教程(7) History历史对象
2009/01/31 Javascript
uploadify在Firefox下丢失session问题的解决方法
2013/08/07 Javascript
SeaJS入门教程系列之SeaJS介绍(一)
2014/03/03 Javascript
jquery网页回到顶部效果(图标渐隐,自写)
2014/06/16 Javascript
浅谈JavaScript字符串拼接
2015/06/25 Javascript
基于JavaScript代码实现pc与手机之间的跳转
2015/12/23 Javascript
Angularjs中$http以post请求通过消息体传递参数的实现方法
2016/08/05 Javascript
微信小程序 新建登录页并实现tabBar隐藏
2017/06/13 Javascript
微信小程序之蓝牙的链接
2017/09/26 Javascript
vue 双向数据绑定的实现学习之监听器的实现方法
2018/11/30 Javascript
Node.js Windows Binary二进制文件安装方法
2019/05/16 Javascript
Vue的路由及路由钩子函数的实现
2019/07/02 Javascript
javascript 设计模式之组合模式原理与应用详解
2020/04/08 Javascript
浅谈python socket函数中,send与sendall的区别与使用方法
2017/05/09 Python
centos 安装python3.6环境并配置虚拟环境的详细教程
2018/02/22 Python
Python 多线程搜索txt文件的内容,并写入搜到的内容(Lock)方法
2019/08/23 Python
pandas中read_csv的缺失值处理方式
2019/12/19 Python
使用python求解二次规划的问题
2020/02/29 Python
python3字符串输出常见面试题总结
2020/12/01 Python
css3实现元素环绕中心点布局的方法示例
2019/01/15 HTML / CSS
实习生个人的自我评价
2013/12/08 职场文书
直接有效的自我评价
2014/01/11 职场文书
运动会广播稿200字
2014/01/15 职场文书
毕业生求职自荐书范文
2014/03/27 职场文书
装修协议书范本
2014/04/21 职场文书
买卖协议书范本
2014/04/21 职场文书
师范类求职信
2014/06/21 职场文书
成品仓库管理员岗位职责
2015/04/09 职场文书
亮剑观后感500字
2015/06/05 职场文书
《分一些蚊子进来》读后感3篇
2020/01/09 职场文书
Javascript设计模式之原型模式详细
2021/10/05 Javascript
Mysql存储过程、触发器、事件调度器使用入门指南
2022/01/22 MySQL
SQL Server内存机制浅探
2022/04/06 SQL Server