html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】


Posted in jQuery onSeptember 10, 2019

电脑和手机移动端都适用的jQuery拖动滑块图片拼图验证码插件,通过鼠标拖动或触屏滑动填充拼图来进行安全验证,点击刷新可以更换当前待验证的图片。

HTML & css:

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖动滑块图片拼图验证码插件</title>
<!--框架样式-->
<link href="css/bootstrap.min.css" rel="external nofollow" rel="stylesheet">
<!--图标样式-->
<link href="https://cdn.bootcss.com/font-awesome/5.7.2/css/all.min.css" rel="stylesheet">
<!--主要样式-->
<link href="disk/slidercaptcha.css" rel="external nofollow" rel="stylesheet" />
<style>
.slidercaptcha {
 margin: 0 auto;
 width: 314px;
 height: 286px;
 border-radius: 4px;
 box-shadow: 0 0 10px rgba(0, 0, 0, 0.125);
 margin-top: 40px;
}
.slidercaptcha .card-body {
 padding: 1rem;
}
.slidercaptcha canvas:first-child {
 border-radius: 4px;
 border: 1px solid #e6e8eb;
}
.slidercaptcha.card .card-header {
 background-image: none;
 background-color: rgba(0, 0, 0, 0.03);
}
.refreshIcon {
 top: -54px;
}
</style>
</head>
<body>
<div class="container-fluid">
 <div class="form-row">
 <div class="col-12">
 <div class="slidercaptcha card">
 <div class="card-header">
  <span>请完成安全验证</span>
 </div>
 <div class="card-body"><div id="captcha"></div></div>
 </div>
 </div>
 </div>
</div>
<script src="js/jquery-1.11.0.min.js" type="text/javascript"></script>
<script src="disk/longbow.slidercaptcha.js"></script>
<script>
 $('#captcha').sliderCaptcha({
 repeatIcon: 'fa fa-redo',
 setSrc: function () {
 return 'http://images.sdgxgz.com/Pic' + Math.round(Math.random() * 136) + '.jpg';
 },
 onSuccess: function () {
 alert('验证通过!');
 }
 });
</script>
</body>
</html>
disk/slidercaptcha.css:
body {
 overflow-x: hidden;
}
.block {
 position: absolute;
 left: 0;
 top: 0;
}
.sliderContainer {
 position: relative;
 text-align: center;
 line-height: 40px;
 background: #f7f9fa;
 color: #45494c;
 border-radius: 2px;
}
.sliderbg {
 position: absolute;
 left: 0;
 right: 0;
 top: 0;
 background-color: #f7f9fa;
 height: 40px;
 border-radius: 2px;
 border: 1px solid #e6e8eb;
}
.sliderContainer_active .slider {
 top: -1px;
 border: 1px solid #1991FA;
}
.sliderContainer_active .sliderMask {
 border-width: 1px 0 1px 1px;
}
.sliderContainer_success .slider {
 top: -1px;
 border: 1px solid #52CCBA;
 background-color: #52CCBA !important;
}
.sliderContainer_success .sliderMask {
 border: 1px solid #52CCBA;
 border-width: 1px 0 1px 1px;
 background-color: #D2F4EF;
}
.sliderContainer_success .sliderIcon:before {
 content: "\f00c";
}
.sliderContainer_fail .slider {
 top: -1px;
 border: 1px solid #f57a7a;
 background-color: #f57a7a !important;
}
.sliderContainer_fail .sliderMask {
 border: 1px solid #f57a7a;
 background-color: #fce1e1;
 border-width: 1px 0 1px 1px;
}
.sliderContainer_fail .sliderIcon:before {
 content: "\f00d";
}
.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;
 border-radius: 2px;
}
.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;
 border-radius: 2px;
 display: flex;
 align-items: center;
 justify-content: center;
}
.slider:hover {
 background: #1991FA;
}
.slider:hover .sliderIcon {
 background-position: 0 -13px;
}
.sliderText {
 position: relative;
}
.sliderIcon {
}
.refreshIcon {
 position: absolute;
 right: 0;
 top: 0;
 cursor: pointer;
 margin: 6px;
 color: rgba(0,0,0,.25);
 font-size: 1rem;
 z-index: 5;
 transition: color .3s linear;
}
 .refreshIcon:hover {
 color: #6c757d;
 }
disk/longbow.slidercaptcha.js:
(function ($) {
 'use strict';
 var SliderCaptcha = function (element, options) {
  this.$element = $(element);
  this.options = $.extend({}, SliderCaptcha.DEFAULTS, options);
  this.$element.css({ 'position': 'relative', 'width': this.options.width + 'px', 'margin': '0 auto' });
  this.init();
 };
 SliderCaptcha.VERSION = '1.0';
 SliderCaptcha.Author = 'argo@163.com';
 SliderCaptcha.DEFAULTS = {
  width: 280,  // canvas宽度
  height: 155, // canvas高度
  PI: Math.PI,
  sliderL: 42, // 滑块边长
  sliderR: 9,  // 滑块半径
  offset: 5,  // 容错偏差
  loadingText: '正在加载中...',
  failedText: '再试一次',
  barText: '向右滑动填充拼图',
  repeatIcon: 'fa fa-repeat',
  maxLoadCount: 3,
  localImages: function () {
   return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
  }
 };
 function Plugin(option) {
  return this.each(function () {
   var $this = $(this);
   var data = $this.data('lgb.SliderCaptcha');
   var options = typeof option === 'object' && option;
   if (data && !/reset/.test(option)) return;
   if (!data) $this.data('lgb.SliderCaptcha', data = new SliderCaptcha(this, options));
   if (typeof option === 'string') data[option]();
  });
 }
 $.fn.sliderCaptcha = Plugin;
 $.fn.sliderCaptcha.Constructor = SliderCaptcha;
 var _proto = SliderCaptcha.prototype;
 _proto.init = function () {
  this.initDOM();
  this.initImg();
  this.bindEvents();
 };
 _proto.initDOM = function () {
  var createElement = function (tagName, className) {
   var elment = document.createElement(tagName);
   elment.className = className;
   return elment;
  };
  var createCanvas = function (width, height) {
   var canvas = document.createElement('canvas');
   canvas.width = width;
   canvas.height = height;
   return canvas;
  };
  var canvas = createCanvas(this.options.width - 2, this.options.height) // 画布
  var block = canvas.cloneNode(true) // 滑块
  var sliderContainer = createElement('div', 'sliderContainer');
  var refreshIcon = createElement('i', 'refreshIcon ' + this.options.repeatIcon);
  var sliderMask = createElement('div', 'sliderMask');
  var sliderbg = createElement('div', 'sliderbg');
  var slider = createElement('div', 'slider');
  var sliderIcon = createElement('i', 'fa fa-arrow-right sliderIcon');
  var text = createElement('span', 'sliderText');
  block.className = 'block'
  text.innerHTML = this.options.barText;
  var el = this.$element;
  el.append($(canvas));
  el.append($(refreshIcon));
  el.append($(block));
  slider.appendChild(sliderIcon);
  sliderMask.appendChild(slider);
  sliderContainer.appendChild(sliderbg);
  sliderContainer.appendChild(sliderMask);
  sliderContainer.appendChild(text);
  el.append($(sliderContainer));
  Object.assign(this, {
   canvas,
   block,
   sliderContainer: $(sliderContainer),
   refreshIcon,
   slider,
   sliderMask,
   sliderIcon,
   text: $(text),
   canvasCtx: canvas.getContext('2d'),
   blockCtx: block.getContext('2d')
  })
 };
 _proto.initImg = function () {
  var that = this;
  var isIE = window.navigator.userAgent.indexOf('Trident') > -1;
  var L = this.options.sliderL + this.options.sliderR * 2 + 3; // 滑块实际边长
  var drawImg = function (ctx, operation) {
   var l = that.options.sliderL;
   var r = that.options.sliderR;
   var PI = that.options.PI;
   var x = that.x;
   var y = that.y;
   ctx.beginPath()
   ctx.moveTo(x, y)
   ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
   ctx.lineTo(x + l, y)
   ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
   ctx.lineTo(x + l, y + l)
   ctx.lineTo(x, y + l)
   ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
   ctx.lineTo(x, y)
   ctx.lineWidth = 2
   ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
   ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'
   ctx.stroke()
   ctx[operation]()
   ctx.globalCompositeOperation = isIE ? 'xor' : 'overlay'
  }
  var getRandomNumberByRange = function (start, end) {
   return Math.round(Math.random() * (end - start) + start);
  };
  var img = new Image();
  img.crossOrigin = "Anonymous";
  var loadCount = 0;
  img.onload = function () {
   // 随机创建滑块的位置
   that.x = getRandomNumberByRange(L + 10, that.options.width - (L + 10));
   that.y = getRandomNumberByRange(10 + that.options.sliderR * 2, that.options.height - (L + 10));
   drawImg(that.canvasCtx, 'fill');
   drawImg(that.blockCtx, 'clip');
   that.canvasCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
   that.blockCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
   var y = that.y - that.options.sliderR * 2 - 1;
   var ImageData = that.blockCtx.getImageData(that.x - 3, y, L, L);
   that.block.width = L;
   that.blockCtx.putImageData(ImageData, 0, y);
   that.text.text(that.text.attr('data-text'));
  };
  img.onerror = function () {
   loadCount++;
   if (window.location.protocol === 'file:') {
    loadCount = that.options.maxLoadCount;
    console.error("can't load pic resource file from File protocal. Please try http or https");
   }
   if (loadCount >= that.options.maxLoadCount) {
    that.text.text('加载失败').addClass('text-danger');
    return;
   }
   img.src = that.options.localImages();
  };
  img.setSrc = function () {
   var src = '';
   loadCount = 0;
   that.text.removeClass('text-danger');
   if ($.isFunction(that.options.setSrc)) src = that.options.setSrc();
   if (!src || src === '') src = 'https://picsum.photos/' + that.options.width + '/' + that.options.height + '/?image=' + Math.round(Math.random() * 20);
   if (isIE) { // IE浏览器无法通过img.crossOrigin跨域,使用ajax获取图片blob然后转为dataURL显示
    var xhr = new XMLHttpRequest()
    xhr.onloadend = function (e) {
     var file = new FileReader(); // FileReader仅支持IE10+
     file.readAsDataURL(e.target.response);
     file.onloadend = function (e) {
      img.src = e.target.result;
     }
    }
    xhr.open('GET', src);
    xhr.responseType = 'blob';
    xhr.send();
   } else img.src = src;
  };
  img.setSrc();
  this.text.attr('data-text', this.options.barText);
  this.text.text(this.options.loadingText);
  this.img = img
 };
 _proto.clean = function () {
  this.canvasCtx.clearRect(0, 0, this.options.width, this.options.height);
  this.blockCtx.clearRect(0, 0, this.options.width, this.options.height);
  this.block.width = this.options.width;
 };
 _proto.bindEvents = function () {
  var that = this;
  this.$element.on('selectstart', function () {
   return false;
  });
  $(this.refreshIcon).on('click', function () {
   that.text.text(that.options.barText);
   that.reset();
   if ($.isFunction(that.options.onRefresh)) that.options.onRefresh.call(that.$element);
  });
  var originX, originY, trail = [],
   isMouseDown = false
  var handleDragStart = function (e) {
   if (that.text.hasClass('text-danger')) return;
   originX = e.clientX || e.touches[0].clientX;
   originY = e.clientY || e.touches[0].clientY;
   isMouseDown = true;
  };
  var handleDragMove = function (e) {
   if (!isMouseDown) return false;
   var eventX = e.clientX || e.touches[0].clientX;
   var eventY = e.clientY || e.touches[0].clientY;
   var moveX = eventX - originX;
   var moveY = eventY - originY;
   if (moveX < 0 || moveX + 40 > that.options.width) return false;
   that.slider.style.left = (moveX - 1) + 'px';
   var blockLeft = (that.options.width - 40 - 20) / (that.options.width - 40) * moveX;
   that.block.style.left = blockLeft + 'px';
   that.sliderContainer.addClass('sliderContainer_active');
   that.sliderMask.style.width = (moveX + 4) + 'px';
   trail.push(moveY);
  };
  var handleDragEnd = function (e) {
   if (!isMouseDown) return false
   isMouseDown = false
   var eventX = e.clientX || e.changedTouches[0].clientX
   if (eventX == originX) return false
   that.sliderContainer.removeClass('sliderContainer_active');
   that.trail = trail
   var {
    spliced,
    verified
   } = that.verify()
   if (spliced && verified) {
    that.sliderContainer.addClass('sliderContainer_success');
    if ($.isFunction(that.options.onSuccess)) that.options.onSuccess.call(that.$element);
   } else {
    that.sliderContainer.addClass('sliderContainer_fail');
    if ($.isFunction(that.options.onFail)) that.options.onFail.call(that.$element);
    setTimeout(() => {
     that.text.text(that.options.failedText);
     that.reset();
    }, 1000);
   }
  };
  this.slider.addEventListener('mousedown', handleDragStart);
  this.slider.addEventListener('touchstart', handleDragStart);
  document.addEventListener('mousemove', handleDragMove);
  document.addEventListener('touchmove', handleDragMove);
  document.addEventListener('mouseup', handleDragEnd);
  document.addEventListener('touchend', handleDragEnd);
  document.addEventListener('mousedown', function () { return false; });
  document.addEventListener('touchstart', function () { return false; });
 };
 _proto.verify = function () {
  var sum = function (x, y) { return x + y; };
  var square = function (x) { return x * x; };
  var arr = this.trail // 拖动时y轴的移动距离
  var average = arr.reduce(sum) / arr.length;
  var deviations = arr.map(function (x) { return x - average; });
  var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
  var left = parseInt(this.block.style.left);
  return {
   spliced: Math.abs(left - this.x) < this.options.offset,
   verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
  }
 };
 _proto.reset = function () {
  this.sliderContainer.removeClass('sliderContainer_fail sliderContainer_success');
  this.slider.style.left = 0
  this.block.style.left = 0
  this.sliderMask.style.width = 0
  this.clean()
  this.text.attr('data-text', this.text.text());
  this.text.text(this.options.loadingText);
  this.img.setSrc();
 };
})(jQuery);

附录1:需要注意css和js的引用路径;

总结

以上所述是小编给大家介绍的html+jQuery实现拖动滑块图片拼图验证码插件希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

jQuery 相关文章推荐
jQuery实现动态删除LI的方法
May 30 jQuery
jQuery插件DataTables分页开发心得体会
Aug 22 jQuery
使用vue与jquery实时监听用户输入状态的操作代码
Sep 19 jQuery
jQuery动态添加元素无法触发绑定事件的解决方法分析
Jan 02 jQuery
jQuery中库的引用方法
Jan 06 jQuery
jQuery实现鼠标滑过商品小图片上显示对应大图片功能【测试可用】
Apr 27 jQuery
jquery实现动态添加附件功能
Oct 23 jQuery
jQuery实现经典的网页3D轮播图封装功能【附源码下载】
Feb 15 jQuery
JQuery 实现文件下载的常用方法分析
Oct 29 jQuery
Jquery Datatables的使用详解
Jan 30 jQuery
jQuery实现的解析本地 XML 文档操作示例
Apr 30 jQuery
jQuery实现动态向上滚动
Dec 21 jQuery
jQuery实现每日秒杀商品倒计时功能
Sep 06 #jQuery
JS秒杀倒计时功能完整实例【使用jQuery3.1.1】
Sep 03 #jQuery
JavaScript自动生成 年月范围 选择功能完整示例【基于jQuery插件】
Sep 03 #jQuery
Jquery动态列功能完整实例
Aug 30 #jQuery
jQuery - AJAX load() 实例用法详解
Aug 27 #jQuery
jQuery实现判断滚动条滚动到document底部的方法分析
Aug 27 #jQuery
解决jquery validate 验证不通过后验证正确的信息仍残留在label上的方法
Aug 27 #jQuery
You might like
PHP+ajax 无刷新删除数据
2010/02/20 PHP
php下安装配置fckeditor编辑器的方法
2011/03/02 PHP
php修改时间格式的代码
2011/05/29 PHP
PHP判断密码强度的方法详解
2017/05/26 PHP
php实现的统计字数函数定义与使用示例
2017/07/26 PHP
PHP 断点续传实例详解
2017/11/11 PHP
Laravel中如何轻松容易的输出完整的SQL语句
2020/07/26 PHP
使javascript也能包含文件
2006/10/26 Javascript
让任务管理器中的CPU跳舞的js代码
2008/11/01 Javascript
js loading加载效果实现代码
2009/11/24 Javascript
$.each与$().each的区别示例介绍
2014/03/20 Javascript
通过$(this)使用jQuery包装后的方法或属性
2014/05/18 Javascript
javascript学习总结之js使用技巧
2015/09/02 Javascript
浅谈javascript控制HTML5的全屏操控,浏览器兼容的问题
2016/10/10 Javascript
浅谈js之字面量、对象字面量的访问、关键字in的用法
2016/11/20 Javascript
完美解决JS文件页面加载时的阻塞问题
2016/12/18 Javascript
通过BootStrap-select插件 js jQuery控制select属性变化
2017/01/03 Javascript
javascript设计模式之Adapter模式【适配器模式】实现方法示例
2017/01/13 Javascript
socket.io学习教程之基础介绍(一)
2017/04/29 Javascript
lhgcalendar时间插件限制只能选择三个月的实现方法
2017/07/03 Javascript
jQuery实现IE输入框完成placeholder标签功能的方法
2017/09/20 jQuery
React Native中的RefreshContorl下拉刷新使用
2017/10/09 Javascript
在Vue组件中获取全局的点击事件方法
2018/09/06 Javascript
在vue中根据光标的显示与消失实现下拉列表
2019/09/29 Javascript
JavaScript实现Tab标签页切换的最简便方式(4种)
2020/06/28 Javascript
[07:09]2014DOTA2国际邀请赛-Newbee再次发威成功晋级决赛
2014/07/19 DOTA
[57:12]完美世界DOTA2联赛循环赛 Inki vs Matador BO2第一场 10.31
2020/11/02 DOTA
Python中的Classes和Metaclasses详解
2015/04/02 Python
python中字典(Dictionary)用法实例详解
2015/05/30 Python
详解Python循环作用域与闭包
2019/03/21 Python
python 变量初始化空列表的例子
2019/11/28 Python
Python PyQt5模块实现窗口GUI界面代码实例
2020/05/12 Python
HTML5+CSS3实现拖放(Drag and Drop)示例
2014/07/07 HTML / CSS
一套中级Java程序员笔试题
2015/01/14 面试题
教师党员公开承诺书
2014/03/25 职场文书
Python绘制地图神器folium的新人入门指南
2021/05/23 Python