Skip to content

Instantly share code, notes, and snippets.

@ahmadrosid
Last active November 22, 2023 04:48
Show Gist options
  • Save ahmadrosid/802b18f3a4539daa4cd20b24fa3efafa to your computer and use it in GitHub Desktop.
Save ahmadrosid/802b18f3a4539daa4cd20b24fa3efafa to your computer and use it in GitHub Desktop.

Setup slider catpcha

First link assets to public/index.html

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"
integrity="sha512-5A8nwdMOWrSz20fDsjczgUidUBR8liPYU+WymTZP1lmY9G6Oc7HlZv156XqnsgNUzTyMefFTcsFH/tnJE/+xBg=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href='%PUBLIC_URL%/captcha.css' rel='stylesheet'>
<script src="%PUBLIC_URL%/slidercaptcha.js"></script>

Then include this file to public folder.

.
├── captcha.css
├── index.html
└── slidercaptcha.js

Then create component SliderCaptcha.js place anywhere you like in your components folder.

Usage:

++ import SliderCaptcha from 'src/app/SliderCaptcha';

const App = () => {
  ...
  return (
    <CacheProvider value={createCache(emotionCacheOptions[langDirection])}>
              ...
++              <SliderCaptcha />
              ...
    </CacheProvider>
  );
};

export default withAppProviders(App)();
.block {
position: absolute;
left: 0;
top: 0;
}
.slidercaptcha {
margin: 0 auto;
width: 100%;
height: 300px;
border-radius: 4px;
margin-top: 0;
}
.slidercaptcha canvas:first-child {
border-radius: 5px;
border: 1px solid #e6e8eb;
}
.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 #02c076;
background-color: #02c076 !important;
color: #fff;
}
.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 #f35c59;
background-color: #f35c59;
color: #fff;
}
.sliderContainer_fail .sliderMask {
border: 1px solid #f35c59;
background-color: #f7dcdd;
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 #d1e9fe;
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: #009efb;
color: #fff;
border-color: #009efb;
}
.slider:hover .sliderIcon {
background-position: 0 -13px;
}
.sliderText {
position: relative;
}
.sliderIcon {
}
.refreshIcon {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
padding: 6px;
color: #fff;
background-color: #ff4c4c;
font-size: 14px;
border-radius: 50px;
}
.refreshIcon:hover {
color: #fff;
}
import { useEffect, useRef, useState } from 'react';
export default function SliderCaptcha() {
const ref = useRef();
const captcha = useRef();
const [keyRender, resetKeyRender] = useState(0);
const toggleCatpcha = () => {
if (captcha.current) {
captcha.current = null;
resetKeyRender(prev => prev+1);
return;
}
if (ref.current && !captcha.current) {
captcha.current = window.sliderCaptcha(
{
element: ref.current,
loadingText: 'Loading...',
failedText: 'Try again',
barText: 'Slide right to fill',
repeatIcon: 'fa fa-redo',
onSuccess: () => {
setTimeout( () => {
alert('Your captcha is successfully verified.');
captcha.current.reset();
}, 1000);
},
})
}
}
useEffect(() => {
if (ref.current && !captcha.current) {
captcha.current = window.sliderCaptcha(
{
element: ref.current,
loadingText: 'Loading...',
failedText: 'Try again',
barText: 'Slide right to fill',
repeatIcon: 'fa fa-redo',
onSuccess: () => {
setTimeout(() => {
alert('Your captcha is successfully verified.');
captcha.current.reset();
}, 1000);
},
})
}
}, []);
return (
<div>
<div key={keyRender} ref={ref} />
<div className="card">
<button type="button" onClick={toggleCatpcha}>Toggle Captcha</button>
</div>
</div>
)
}
(function () {
'use strict';
var extend = function () {
var length = arguments.length;
var target = arguments[0] || {};
if (typeof target != "object" && typeof target != "function") {
target = {};
}
if (length == 1) {
target = this;
i--;
}
for (var i = 1; i < length; i++) {
var source = arguments[i];
for (var key in source) {
// For in will iterate over all enumerable properties of an array, including those inherited from the prototype.
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
}
var isFunction = function isFunction(obj) {
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
var SliderCaptcha = function (element, options) {
this.$element = element;
this.options = extend({}, SliderCaptcha.DEFAULTS, options);
this.$element.style.position = 'relative';
this.$element.style.width = this.options.width + 'px';
this.$element.style.margin = '0 auto';
this.init();
};
SliderCaptcha.VERSION = '1.0';
SliderCaptcha.Author = 'argo@163.com';
SliderCaptcha.DEFAULTS = {
width: 280,
height: 155,
PI: Math.PI,
sliderL: 42,
sliderR: 9,
offset: 5,
loadingText: 'Loading...',
failedText: 'Try again',
barText: 'Slide right to fill',
repeatIcon: 'fa fa-repeat',
maxLoadCount: 3,
localImages: function () {
return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
},
verify: function (arr, url) {
var ret = false;
console.log("verify", arr, url)
return ret;
},
remoteUrl: null
};
function Plugin(option) {
var $this = option.id ? document.getElementById(option.id): option.element;
var options = typeof option === 'object' && option;
return new SliderCaptcha($this, options);
}
window.sliderCaptcha = Plugin;
window.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); // Slider
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.appendChild(canvas);
el.appendChild(refreshIcon);
el.appendChild(block);
slider.appendChild(sliderIcon);
sliderMask.appendChild(slider);
sliderContainer.appendChild(sliderbg);
sliderContainer.appendChild(sliderMask);
sliderContainer.appendChild(text);
el.appendChild(sliderContainer);
var _canvas = {
canvas: canvas,
block: block,
sliderContainer: sliderContainer,
refreshIcon: refreshIcon,
slider: slider,
sliderMask: sliderMask,
sliderIcon: sliderIcon,
text: text,
canvasCtx: canvas.getContext('2d'),
blockCtx: block.getContext('2d')
};
if (isFunction(Object.assign)) {
Object.assign(this, _canvas);
}
else {
extend(this, _canvas);
}
};
_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' : 'destination-over';
};
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 + 1);
that.text.textContent = that.text.getAttribute('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.textContent = '加载失败';
that.classList.add('text-danger');
return;
}
img.src = that.options.localImages();
};
img.setSrc = function () {
var src = '';
loadCount = 0;
that.text.classList.remove('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.setAttribute('data-text', this.options.barText);
this.text.textContent = 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.addEventListener('selectstart', function () {
return false;
});
this.refreshIcon.addEventListener('click', function () {
that.text.textContent = 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.classList.contains('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.classList.add('sliderContainer_active');
that.sliderMask.style.width = (moveX + 4) + 'px';
trail.push(Math.round(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.classList.remove('sliderContainer_active');
that.trail = trail;
var data = that.verify();
if (data.spliced && data.verified) {
that.sliderContainer.classList.add('sliderContainer_success');
if (isFunction(that.options.onSuccess)) that.options.onSuccess.call(that.$element);
} else {
that.sliderContainer.classList.add('sliderContainer_fail');
if (isFunction(that.options.onFail)) that.options.onFail.call(that.$element);
setTimeout(function () {
that.text.innerHTML = 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; });
document.addEventListener('swipe', function () { return false; });
};
_proto.verify = function () {
var arr = this.trail; // 拖动时y轴的移动距离
var left = parseInt(this.block.style.left);
var verified = false;
if (this.options.remoteUrl !== null) {
verified = this.options.verify(arr, this.options.remoteUrl);
}
else {
var sum = function (x, y) { return x + y; };
var square = function (x) { return x * x; };
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);
verified = stddev !== 0;
}
return {
spliced: Math.abs(left - this.x) < this.options.offset,
verified: verified
};
};
_proto.reset = function () {
this.sliderContainer.classList.remove('sliderContainer_fail');
this.sliderContainer.classList.remove('sliderContainer_success');
this.slider.style.left = 0;
this.block.style.left = 0;
this.sliderMask.style.width = 0;
this.clean();
this.text.setAttribute('data-text', this.text.textContent);
this.text.textContent = this.options.loadingText;
this.img.setSrc();
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment