UNPKG

vue-next-slider-vertify

Version:

基于 Vue3 的滑动验证码组件

413 lines (412 loc) 19.5 kB
import { defineComponent, ref, watchEffect, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, toDisplayString, unref } from 'vue' function getRandomNumberByRange(start, end) { return Math.round(Math.random() * (end - start) + start) } function sum(x, y) { return x + y } function square(x) { return x * x } const successIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0yNCA0NEMzNS4wNDU3IDQ0IDQ0IDM1LjA0NTcgNDQgMjRDNDQgMTIuOTU0MyAzNS4wNDU3IDQgMjQgNEMxMi45NTQzIDQgNCAxMi45NTQzIDQgMjRDNCAzNS4wNDU3IDEyLjk1NDMgNDQgMjQgNDRaIiBmaWxsPSIjMmZkMzIxIiBzdHJva2U9IiM3ZWQzMjEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMSAxOFYxOSIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNyAxOFYxOSIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMSAzMUMzMSAzMSAyOSAzNSAyNCAzNUMxOSAzNSAxNyAzMSAxNyAzMSIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==' const errorIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0yNCA0NEMzNS4wNDU3IDQ0IDQ0IDM1LjA0NTcgNDQgMjRDNDQgMTIuOTU0MyAzNS4wNDU3IDQgMjQgNEMxMi45NTQzIDQgNCAxMi45NTQzIDQgMjRDNCAzNS4wNDU3IDEyLjk1NDMgNDQgMjQgNDRaIiBmaWxsPSIjMmZkMzIxIiBzdHJva2U9IiM3ZWQzMjEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMSAxOFYxOSIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNyAxOFYxOSIgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMSAzMC45OTk5QzMxIDMwLjk5OTkgMjkgMjYuOTk5OSAyNCAyNi45OTk5QzE5IDI2Ljk5OTkgMTcgMzAuOTk5OSAxNyAzMC45OTk5IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+' const loadingIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0yNCA0NEMzNS4wNDU3IDQ0IDQ0IDM1LjA0NTcgNDQgMjRDNDQgMTIuOTU0MyAzNS4wNDU3IDQgMjQgNEMxMi45NTQzIDQgNCAxMi45NTQzIDQgMjRDNCAzNS4wNDU3IDEyLjk1NDMgNDQgMjQgNDRaIiBmaWxsPSIjMmZkMzIxIiBzdHJva2U9IiM3ZWQzMjEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTI0IDEyVjE1IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTMyLjQ4NTMgMTUuNTE0N0wzMC4zNjQgMTcuNjM2IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTM2IDI0SDMzIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTMyLjQ4NTMgMzIuNDg1M0wzMC4zNjQgMzAuMzY0IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTI0IDM2VjMzIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTE1LjUxNDcgMzIuNDg1M0wxNy42MzYgMzAuMzY0IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTEyIDI0SDE1IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTE1LjUxNDcgMTUuNTE0N0wxNy42MzYgMTcuNjM2IiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+' const _hoisted_1 = { class: 'canvas-area' } const _hoisted_2 = ['width', 'height'] const _hoisted_3 = ['width', 'height'] const _hoisted_4 = /* @__PURE__ */ createElementVNode( 'div', { class: 'slider-icon' }, '\u2192', -1 ) const _hoisted_5 = [_hoisted_4] const _hoisted_6 = { class: 'slider-text' } const _hoisted_7 = ['src'] const _hoisted_8 = /* @__PURE__ */ createElementVNode('span', null, '\u52A0\u8F7D\u4E2D...', -1) const _hoisted_9 = ['src'] const _sfc_main = /* @__PURE__ */ defineComponent({ __name: 'index', props: { width: { default: 320 }, height: { default: 160 }, l: { default: 42 }, r: { default: 9 }, visible: { type: Boolean, default: true }, text: { default: '\u5411\u53F3\u6ED1\u52A8\u6ED1\u5757\u5B8C\u6210\u62FC\u56FE' }, refreshIcon: { default: 'http://cdn.dooring.cn/dr/icon12.png' }, imgUrl: null, onDraw: null, onCustomVertify: null, onSuccess: null, onFail: null, onRefresh: null }, setup(__props) { const state = ref(0) const sliderLeft = ref(0) const sliderClass = ref('slider-container') const textTip = ref(__props.text) const canvasRef = ref() const blockRef = ref() const imgRef = ref() const isMouseDownRef = ref(false) const trailRef = ref([]) const originXRef = ref(0) const originYRef = ref(0) const xRef = ref(0) const yRef = ref(0) const vertifySuccess = ref(0) const PI = Math.PI const L = __props.l + __props.r * 2 + 3 function setState(s) { state.value = s } function setSliderLeft(sliderleft) { sliderLeft.value = sliderleft } function setSliderClass(sliderclass) { sliderClass.value = sliderclass } function drawPath(ctx, x, y, operation) { ctx.beginPath() ctx.moveTo(x, y) ctx.arc(x + __props.l / 2, y - __props.r + 2, __props.r, 0.72 * PI, 2.26 * PI) ctx.lineTo(x + __props.l, y) ctx.arc(x + __props.l + __props.r - 2, y + __props.l / 2, __props.r, 1.21 * PI, 2.78 * PI) ctx.lineTo(x + __props.l, y + __props.l) ctx.lineTo(x, y + __props.l) ctx.arc(x + __props.r - 2, y + __props.l / 2, __props.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.globalCompositeOperation = 'destination-over' operation === 'fill' ? ctx.fill() : ctx.clip() } function getRandomImgSrc() { return __props.imgUrl || `https://picsum.photos/id/${getRandomNumberByRange(0, 1084)}/${__props.width}/${__props.height}` } function createImg(onload) { const img = new Image() img.crossOrigin = 'Anonymous' img.onload = onload img.onerror = () => { img.setSrc(getRandomImgSrc()) } img.setSrc = src => { const isIE = window.navigator.userAgent.indexOf('Trident') > -1 if (isIE) { const xhr = new XMLHttpRequest() xhr.onloadend = function (e) { const file = new FileReader() file.readAsDataURL(e.target.response) file.onloadend = function (e2) { let _a img.src = (_a = e2 == null ? void 0 : e2.target) == null ? void 0 : _a.result } } xhr.open('GET', src) xhr.responseType = 'blob' xhr.send() } else { img.src = src } } img.setSrc(getRandomImgSrc()) return img } function draw(img) { const canvasCtx = canvasRef.value.getContext('2d') const blockCtx = blockRef.value.getContext('2d') xRef.value = getRandomNumberByRange(L + 10, __props.width - (L + 10)) yRef.value = getRandomNumberByRange(10 + __props.r * 2, __props.height - (L + 10)) drawPath(canvasCtx, xRef.value, yRef.value, 'fill') drawPath(blockCtx, xRef.value, yRef.value, 'clip') canvasCtx.drawImage(img, 0, 0, __props.width, __props.height) blockCtx.drawImage(img, 0, 0, __props.width, __props.height) const y1 = yRef.value - __props.r * 2 - 1 const ImageData = blockCtx.getImageData(xRef.value - 3, y1, L, L) blockRef.value.width = L blockCtx.putImageData(ImageData, 0, y1) } watchEffect(() => { if (__props.visible) { imgRef.value ? reset() : initImg() } }) function initImg() { const img = createImg(() => { setState(1) draw(img) }) imgRef.value = img } function reset() { const canvasCtx = canvasRef.value && canvasRef.value.getContext('2d') const blockCtx = blockRef.value && blockRef.value.getContext('2d') setSliderLeft(0) setSliderClass('slider-container') blockRef.value.width = __props.width blockRef.value.style.left = 0 + 'px' canvasCtx.clearRect(0, 0, __props.width, __props.height) blockCtx.clearRect(0, 0, __props.width, __props.height) setState(0) vertifySuccess.value = 0 imgRef.value && (imgRef.value.src = getRandomImgSrc()) } function handleRefresh() { reset() typeof __props.onRefresh === 'function' && __props.onRefresh() } function verify() { const arr = trailRef.value 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(blockRef.value.style.left) return { spliced: Math.abs(left - xRef.value) < 10, verified: stddev !== 0, left, destX: xRef.value } } function handleDragStart(e) { originXRef.value = e.clientX || e.touches[0].clientX originYRef.value = e.clientY || e.touches[0].clientY isMouseDownRef.value = true } function handleDragMove(e) { if (!isMouseDownRef.value) { return false } e.preventDefault() const eventX = e.clientX || e.touches[0].clientX const eventY = e.clientY || e.touches[0].clientY const moveX = eventX - originXRef.value const moveY = eventY - originYRef.value if (moveX < 0 || moveX + 38 >= __props.width) { return false } setSliderLeft(moveX) const blockLeft = ((__props.width - 40 - 20) / (__props.width - 40)) * moveX blockRef.value.style.left = blockLeft + 'px' setSliderClass('slider-container slider-container_active') trailRef.value.push(moveY) __props.onDraw && __props.onDraw(blockLeft) } function handleDragEnd(e) { if (!isMouseDownRef.value) { return false } isMouseDownRef.value = false const eventX = e.clientX || e.changedTouches[0].clientX if (eventX === originXRef.value) { return false } setSliderClass('slider-container') const { spliced, verified } = __props.onCustomVertify ? __props.onCustomVertify(verify()) : verify() if (spliced) { if (verified) { vertifySuccess.value = 1 setSliderClass('slider-container slider-container_success') typeof __props.onSuccess === 'function' && __props.onSuccess() } else { vertifySuccess.value = 2 setSliderClass('slider-container slider-container_fail') textTip.value = '\u8BF7\u518D\u8BD5\u4E00\u6B21' reset() } } else { setSliderClass('slider-container slider-container_fail') typeof __props.onFail === 'function' && __props.onFail() setTimeout(() => { reset() }, 1e3) } } return (_ctx, _cache) => { return ( openBlock(), createElementBlock( 'div', { class: 'slider-vertify-wrap', style: normalizeStyle({ width: __props.width + 'px', margin: '0 auto', display: __props.visible ? '' : 'none' }), onMousemove: handleDragMove, onMouseup: handleDragEnd, onTouchmove: handleDragMove, onTouchend: handleDragEnd }, [ createElementVNode('div', _hoisted_1, [ createElementVNode( 'canvas', { ref_key: 'canvasRef', ref: canvasRef, width: __props.width, height: __props.height }, null, 8, _hoisted_2 ), createElementVNode( 'canvas', { ref_key: 'blockRef', ref: blockRef, class: 'block', width: __props.width, height: __props.height, onMousedown: handleDragStart, onTouchstart: handleDragStart }, null, 40, _hoisted_3 ) ]), createElementVNode( 'div', { class: normalizeClass(sliderClass.value), style: normalizeStyle({ pointerEvents: state.value === 0 ? 'none' : 'auto', width: __props.width + 'px' }) }, [ createElementVNode( 'div', { class: 'slider-mask', style: normalizeStyle({ width: sliderLeft.value + 'px' }) }, [ createElementVNode( 'div', { class: 'slider', style: normalizeStyle({ left: sliderLeft.value + 'px' }), onMousedown: handleDragStart, onTouchstart: handleDragStart }, _hoisted_5, 36 ) ], 4 ), createElementVNode('div', _hoisted_6, toDisplayString(textTip.value), 1) ], 6 ), createElementVNode( 'div', { class: 'refresh-icon', style: normalizeStyle({ backgroundImage: `url(${__props.refreshIcon})` }), onClick: handleRefresh }, null, 4 ), createElementVNode( 'div', { class: 'loading-container', style: normalizeStyle({ width: __props.width + 'px', height: __props.height + 'px', display: state.value === 0 ? '' : 'none' }) }, [ createElementVNode( 'img', { class: 'loadingIcon', src: unref(loadingIcon), alt: 'icon' }, null, 8, _hoisted_7 ), _hoisted_8 ], 4 ), createElementVNode( 'div', { class: 'loading-container', style: normalizeStyle({ width: __props.width + 'px', height: __props.height + 'px', display: state.value !== 0 && vertifySuccess.value !== 0 ? '' : 'none' }) }, [ createElementVNode( 'img', { src: vertifySuccess.value === 1 ? unref(successIcon) : unref(errorIcon), alt: 'icon' }, null, 8, _hoisted_9 ), createElementVNode('span', null, toDisplayString(vertifySuccess.value === 1 ? '\u9A8C\u8BC1\u6210\u529F' : '\u9A8C\u8BC1\u5931\u8D25'), 1) ], 4 ) ], 36 ) ) } } }) const index_vue_vue_type_style_index_0_lang = /* @__PURE__ */ (() => '.slider-vertify-wrap{position:relative}.slider-vertify-wrap .block{position:absolute;left:0;top:0;cursor:pointer;cursor:grab}.slider-vertify-wrap .block:active{cursor:grabbing}.slider-vertify-wrap .slider-container{position:relative;text-align:center;width:310px;height:40px;line-height:40px;background:#f7f9fa;color:#45494c;border:1px solid #e4e7eb;box-sizing:border-box}.slider-vertify-wrap .slider-container_active .slider{height:38px;top:-1px;border:1px solid #486cd6}.slider-vertify-wrap .slider-container_active .slider-mask{height:38px;border-width:1px}.slider-vertify-wrap .slider-container_success .slider{height:38px;top:-1px;border:1px solid #0db87f;background-color:#0ca14a!important}.slider-vertify-wrap .slider-container_success .slider-mask{height:38px;border:1px solid #0db87f;background-color:#d2f4ef}.slider-vertify-wrap .slider-container_success .slider-icon{background-position:0 -26px!important}.slider-vertify-wrap .slider-container_fail .slider{height:38px;top:-1px;border:1px solid #f57a7a;background-color:#f57a7a!important}.slider-vertify-wrap .slider-container_fail .slider-mask{height:38px;border:1px solid #f57a7a;background-color:#fce1e1}.slider-vertify-wrap .slider-container_fail .slider-icon{top:14px;background-position:0 -82px!important}.slider-vertify-wrap .slider-container_active .slider-text,.slider-vertify-wrap .slider-container_success .slider-text,.slider-vertify-wrap .slider-container_fail .slider-text{display:none}.slider-vertify-wrap .slider-mask{position:absolute;left:0;top:0;height:40px;border:0 solid #486cd6;background:#d1e9fe;box-sizing:border-box}.slider-vertify-wrap .slider{position:absolute;top:0;left:0;width:40px;height:38px;background:#fff;box-shadow:0 0 3px #0000004d;transition:background .2s linear;box-sizing:border-box;cursor:pointer;cursor:grab}.slider-vertify-wrap .slider:active{cursor:grabbing}.slider-vertify-wrap .slider:hover{background:#486cd6}.slider-vertify-wrap .slider-icon{font-size:18px;color:#000}.slider-vertify-wrap .slider:hover .slider-icon{color:#fff}.slider-vertify-wrap .refresh-icon{position:absolute;right:5px;top:5px;width:30px;height:30px;cursor:pointer;background-size:32px}.slider-vertify-wrap .loading-container{position:absolute;left:0;top:0;width:310px;height:155px;display:flex;flex-direction:column;justify-content:center;align-items:center;font-size:14px;color:#45494c;z-index:2;background:#edf0f2}.slider-vertify-wrap .loadingIcon{width:32px;height:32px;margin-bottom:10px;background-size:32px;animation:loading-icon-rotate .8s linear infinite}@keyframes loading-icon-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n')() export { _sfc_main as default }