emis-frontend/src/components/SlideVerify/index.vue
2024-02-20 18:10:28 +08:00

449 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="slideVerify" class="slide-verify" :style="widthlable" onselectstart="return false;">
<canvas ref="canvas" :width="w" :height="h" />
<canvas ref="block" class="slide-verify-block" :width="w" :height="h" @click="refresh"/>
<div class="slide-verify-refresh-icon el-icon-refresh" @click="refresh" />
<div class="slide-verify-info" :class="{fail: fail, show: showInfo}" @click="refresh">{{ infoText }}</div>
<div
class="slide-verify-slider"
:style="widthlable"
:class="{'container-active': containerActive, 'container-success': containerSuccess, 'container-fail': containerFail}"
>
<div class="slide-verify-slider-mask" :style="{width: sliderMaskWidth}">
<!-- slider -->
<div
class="slide-verify-slider-mask-item"
:style="{left: sliderLeft}"
@mousedown="sliderDown"
@touchstart="touchStartEvent"
@touchmove="touchMoveEvent"
@touchend="touchEndEvent"
>
<div class="slide-verify-slider-mask-item-icon el-icon-s-unfold" />
</div>
</div>
<span class="slide-verify-slider-text">{{ sliderText }}</span>
</div>
</div>
</template>
<script>
function sum(x, y) {
return x + y
}
function square(x) {
return x * x
}
export default {
name: 'SlideVerify',
props: {
// block length
l: {
type: Number,
default: 42
},
// block radius
r: {
type: Number,
default: 10
},
// canvas width
w: { // 背景图宽
type: [Number, String],
default: 350
},
// canvas height
h: { // 背景图高
type: [Number, String],
default: 200
},
// canvas width
sw: { // 小图宽
type: [Number, String],
default: 50
},
// canvas height
sh: {
type: [Number, String],
default: 50
},
// block_x: {
// type: Number,
// default: 155
// },
blocky: { // 小图初始的垂直距离
type: [Number, String],
default: 20
},
sliderText: {
type: String,
default: 'Slide filled right'
},
imgurl: { // 大图地址
type: String,
default: ''
},
miniimgurl: { // 小图地址
type: String,
default: ''
},
fresh: {
type: Boolean,
default: false
}
},
data() {
return {
containerActive: false, // container active class
containerSuccess: false, // container success class
containerFail: false, // container fail class
canvasCtx: null,
blockCtx: null,
block: null,
canvasStr: null,
// block_x: undefined, // container random position
// blocky: undefined,
L: this.l + this.r * 2 + 3, // block real lenght
img: undefined,
originX: undefined,
originY: undefined,
isMouseDown: false,
trail: [],
widthlable: '',
sliderLeft: 0, // block right offset
sliderMaskWidth: 0, // mask width
dialogVisible: false,
infoText: '验证成功',
fail: false,
showInfo: false
}
},
watch: {
fresh(val) {
if (val) {
this.init()
}
}
},
mounted() {
// 随机生成数this.block_x =
this.init()
},
methods: {
init() {
this.initDom()
this.bindEvents()
this.widthlable = 'width:' + this.w + 'px;'
},
initDom() {
this.block = this.$refs.block
this.canvasStr = this.$refs.canvas
this.canvasCtx = this.canvasStr.getContext('2d')
this.blockCtx = this.block.getContext('2d')
this.initImg()
},
initImg(h) {
var that = this
var wRate = 1.0;
const img = document.createElement('img')
img.onload = onload
img.onerror = () => {
img.src = that.imgurl
}
img.src = that.imgurl
img.onload = function() {
wRate=that.w/img.width;
that.canvasCtx.drawImage(img, 0, 0, that.w, that.h)
}
this.img = img
const img1 = document.createElement('img')
var blockCtx = that.blockCtx
var blocky = h || that.blocky
if (blocky === 0) {
return
}
img1.onerror = () => {
img1.src = that.miniimgurl
}
img1.src = that.miniimgurl
img1.onload = function() {
var blockDrawY=blocky*wRate;
var blockDrawW=img1.width*wRate;
var blockDrawH=img1.height*wRate;
blockCtx.drawImage(img1, 0, blockDrawY, blockDrawW, blockDrawH)
}
},  // 刷新
refresh() {
this.$emit('refresh')
},
sliderDown(event) {
this.originX = event.clientX
this.originY = event.clientY
this.isMouseDown = true
},
touchStartEvent(e) {
this.originX = e.changedTouches[0].pageX
this.originY = e.changedTouches[0].pageY
this.isMouseDown = true
},
bindEvents() {
document.addEventListener('mousemove', e => {
if (!this.isMouseDown) return false
const moveX = e.clientX - this.originX
const moveY = e.clientY - this.originY
if (moveX < 0 || moveX + 38 >= this.w) return false
this.sliderLeft = moveX + 'px'
const blockLeft = ((this.w - 40 - 20) / (this.w - 40)) * moveX
this.block.style.left = blockLeft + 'px'
this.containerActive = true // add active
this.sliderMaskWidth = moveX + 'px'
this.trail.push(moveY)
})
document.addEventListener('mouseup', e => {
if (!this.isMouseDown) return false
this.isMouseDown = false
if (e.clientX === this.originX) return false
this.containerActive = false // remove active
this.verify()
})
},
touchMoveEvent(e) {
if (!this.isMouseDown) return false
const moveX = e.changedTouches[0].pageX - this.originX
const moveY = e.changedTouches[0].pageY - this.originY
if (moveX < 0 || moveX + 38 >= this.w) return false
this.sliderLeft = moveX + 'px'
const blockLeft = ((this.w - 40 - 20) / (this.w - 40)) * moveX
this.block.style.left = blockLeft + 'px'
this.containerActive = true
this.sliderMaskWidth = moveX + 'px'
this.trail.push(moveY)
},
touchEndEvent(e) {
if (!this.isMouseDown) return false
this.isMouseDown = false
if (e.changedTouches[0].pageX === this.originX) return false
this.containerActive = false
this.verify()
},
verify() {
const arr = this.trail // drag y move distance
const average = arr.reduce(sum) / arr.length // average
const deviations = arr.map(x => x - average) // deviation array
const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) // standard deviation
const left = parseInt(this.block.style.left)
this.$emit('success', left, stddev)
},
reset(h) {
this.containerActive = false
this.containerSuccess = false
this.containerFail = false
this.sliderLeft = 0
this.block.style.left = 0
this.sliderMaskWidth = 0
this.canvasCtx.clearRect(0, 0, this.w, this.h)
this.blockCtx.clearRect(0, 0, this.w, this.h)
this.fail = false
this.showInfo = false
this.containerFail = false
this.containerSuccess = false
this.initImg(h)
},
handleFail() {
this.fail = true
this.showInfo = true
this.infoText = '验证失败'
this.containerFail = true
// setTimeout(() => {
// this.block.style.left = 0
// this.sliderMaskWidth = 0
// this.sliderLeft = 0
// this.fail = false
// this.showInfo = false
// this.containerFail = false
// }, 800)
},
handleSuccess() {
this.showInfo = true
this.infoText = '验证成功'
this.containerSuccess = true
setTimeout(() => {
this.block.style.left = 0
this.sliderMaskWidth = 0
this.sliderLeft = 0
this.fail = false
this.showInfo = false
this.containerSuccess = false
}, 1000)
}
}
}
</script>
<style scoped>
.slide-verify {
position: relative;
width: 310px;
overflow: hidden;
}
.slide-verify-block {
position: absolute;
left: 0;
top: 0;
}
.slide-verify-refresh-icon {
position: absolute;
right: 0;
top: 0;
width: 34px;
height: 34px;
cursor: pointer;
content: '刷新';
font-size: 22px;
line-height: 34px;
text-align: center;
font-weight: bold;
color: #1991fa;
/* background: url("../../assets/move/icon_light.png") 0 -437px; */
background-size: 34px 471px;
}
.slide-verify-refresh-icon:hover {
transform: rotate(180deg);
transition: all 0.2s ease-in-out;
}
.slide-verify-slider {
position: relative;
text-align: center;
width: 310px;
height: 40px;
line-height: 40px;
margin-top: 15px;
background: #f7f9fa;
color: #45494c;
border: 1px solid #e4e7eb;
}
.slide-verify-slider-mask {
position: absolute;
left: 0;
top: 0;
height: 40px;
border: 0 solid #1991fa;
background: #d1e9fe;
}
.slide-verify-info {
position: absolute;
top: 170px;
left: 0;
height: 30px;
width: 350px;
color: #fff;
text-align: center;
line-height: 30px;
background-color: #B12D29;
opacity: 0;
}
.slide-verify-info.fail {
background-color: #f00;
}
.slide-verify-info.show {
animation: hide 1s ease;
}
@keyframes hide {
0% {opacity: 0;}
100% {opacity: 0.9;}
}
.slide-verify-slider-mask-item {
position: absolute;
top: 0;
left: 0;
width: 38px;
height: 38px;
background: #fff;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: background 0.2s linear;
}
.slide-verify-slider-mask-item:hover {
background: #1991fa;
}
.slide-verify-slider-mask-item:hover .slide-verify-slider-mask-item-icon {
background-position: 0 -13px;
}
.slide-verify-slider-mask-item-icon {
position: absolute;
top: 9px;
left: 7px;
width: 14px;
height: 12px;
content: '法币';
font-size: 22px;
color: #ddd;
/* text-align: center;
line-height: 12px; */
/* background: url("../../assets/move/icon_light.png") 0 -26px; */
/* background-size: 34px 471px; */
}
.container-active .slide-verify-slider-mask-item {
height: 38px;
top: -1px;
border: 1px solid #1991fa;
}
.container-active .slide-verify-slider-mask {
height: 38px;
border-width: 1px;
}
.container-success .slide-verify-slider-mask-item {
height: 38px;
top: -1px;
border: 1px solid #1991fa;
background-color: #add5f9 !important;
}
.container-success .slide-verify-slider-mask {
height: 38px;
border: 1px solid #1991fa;
background-color: #add5f9;
}
.container-success .slide-verify-slider-mask-item-icon {
background-position: 0 0 !important;
}
.container-fail .slide-verify-slider-mask-item {
height: 38px;
top: -1px;
border: 1px solid #f57a7a;
background-color: #f57a7a !important;
}
.container-fail .slide-verify-slider-mask {
height: 38px;
border: 1px solid #f57a7a;
background-color: #fce1e1;
}
.container-fail .slide-verify-slider-mask-item-icon {
top: 14px;
background-position: 0 -82px !important;
}
.container-active .slide-verify-slider-text,
.container-success .slide-verify-slider-text,
.container-fail .slide-verify-slider-text {
display: none;
}
</style>