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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u014789708/article/details/95215097

电脑和手机移动端都适用的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="stylesheet">

<!--图标样式-->
<link href="https://cdn.bootcss.com/font-awesome/5.7.2/css/all.min.css" rel="stylesheet">

<!--主要样式-->
<link href="disk/slidercaptcha.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="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 = '[email protected]';
    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的引用路径;

猜你喜欢

转载自blog.csdn.net/u014789708/article/details/95215097