创建项目如下:
index.html:
<!DOCTYPE html><html lang="en"><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="/font-awesome/5.7.2/css/all.min.css" rel="stylesheet"><link href="slider/slider.css" 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="jquery.min.js" type="text/javascript"></script><script src="slider/slider.js"></script><script>$('#captcha').sliderCaptcha({repeatIcon: 'fa fa-redo',setSrc:'./imager3.png',onSuccess: function () {alert('验证通过!');}});</script></body></html>
slider.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;}
slider.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@';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 = 2ctx.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 = falsevar 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 falseisMouseDown = falsevar eventX = e.clientX || e.changedTouches[0].clientXif (eventX == originX) return falsethat.sliderContainer.removeClass('sliderContainer_active');that.trail = trailvar {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 = 0this.block.style.left = 0this.sliderMask.style.width = 0this.clean()this.text.attr('data-text', this.text.text());this.text.text(this.options.loadingText);this.img.setSrc();};})(jQuery);