295 lines
7.1 KiB
Vue
295 lines
7.1 KiB
Vue
<template>
|
|
<view class="captcha-container" ref="container">
|
|
<view class="font-title">请通过滑块验证</view>
|
|
<view class="captcha-image" style="position: relative; width: 100%; height: 400rpx; overflow: hidden;">
|
|
<image :src="bgImage" class="bg-image" mode="widthFix" @load="init" />
|
|
<view class="overlay" :style="{width: containerWidth + 'rpx', height: containerHeight + 'rpx'}">
|
|
<view class="hole" :style="{
|
|
top: originY + 'rpx',
|
|
left: (originX +50 ) + 'rpx',
|
|
width: pieceSize + 'rpx',
|
|
height: pieceSize + 'rpx',
|
|
clipPath: clipPath,
|
|
transform: 'translate(-50%, -50%)',
|
|
backgroundColor: 'rgba(0,0,0,0.6)'
|
|
}"></view>
|
|
|
|
<view class="piece" :style="{
|
|
top: originY + 'rpx',
|
|
left: (offsetX +50 ) + 'rpx',
|
|
width: pieceSize + 'rpx',
|
|
height: pieceSize + 'rpx',
|
|
backgroundImage: `url(${bgImage})`,
|
|
backgroundSize: containerWidth + 'rpx ' + containerHeight + 'rpx',
|
|
backgroundPosition: `-${originX+10}rpx -${originY-20}rpx`,
|
|
clipPath: clipPath,
|
|
transform: 'translate(-50%, -50%)'
|
|
}"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="slider-bar">
|
|
<view class="slider-bar-font">
|
|
向右滑动滑块填充拼图
|
|
</view>
|
|
<view class="slider-button" ref="btn" @touchstart.prevent="onStart" @mousedown.prevent="onStart"
|
|
:style="{ left: offsetX + 'rpx', maxWidth: (containerWidth - pieceSize) + 'rpx' }">
|
|
<image src="/static/login/right.png" style="width: 50rpx;height: 50rpx;" mode="widthFix" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ref,
|
|
onBeforeUnmount
|
|
} from 'vue';
|
|
import {
|
|
onLoad
|
|
} from '@dcloudio/uni-app';
|
|
|
|
import img0 from '@/static/login/0.png'
|
|
import img1 from '@/static/login/1.png'
|
|
import img2 from '@/static/login/2.png'
|
|
import img3 from '@/static/login/3.png'
|
|
|
|
const emit = defineEmits(['success'])
|
|
|
|
const pieceSizePx = 50;
|
|
const pieceSize = pieceSizePx * 2;
|
|
const tolerance = 20;
|
|
|
|
const container = ref(null);
|
|
const btn = ref(null);
|
|
|
|
const containerWidthPx = 200;
|
|
const containerHeightPx = 300;
|
|
|
|
const containerWidth = ref(containerWidthPx * 2);
|
|
const containerHeight = ref(containerHeightPx * 2);
|
|
|
|
const originX = ref(0);
|
|
const originY = ref(0);
|
|
const offsetX = ref(0);
|
|
const dragging = ref(false);
|
|
const startX = ref(0);
|
|
|
|
function getPuzzlePiecePath(size) {
|
|
const s = size;
|
|
return `
|
|
M${10*2} 0
|
|
h${s / 3 - 10*2}
|
|
a${10*2} ${10*2} 0 0 1 0 ${20*2}
|
|
h${s / 3}
|
|
a${10*2} ${10*2} 0 0 0 0 -${20*2}
|
|
h${s / 3 - 10*2}
|
|
v${s / 3 - 10*2}
|
|
a${10*2} ${10*2} 0 0 1 -${20*2} 0
|
|
v${s / 3}
|
|
a${10*2} ${10*2} 0 0 0 ${20*2} 0
|
|
v${s / 3 - 10*2}
|
|
h-${s / 3 - 10*2}
|
|
a${10*2} ${10*2} 0 0 1 0 -${20*2}
|
|
h-${s / 3}
|
|
a${10*2} ${10*2} 0 0 0 0 ${20*2}
|
|
h-${s / 3 - 10*2}
|
|
z
|
|
`;
|
|
}
|
|
|
|
const clipPath = `path('${getPuzzlePiecePath(pieceSize)}')`;
|
|
|
|
function init() {
|
|
uni.createSelectorQuery()
|
|
.in(container.value)
|
|
.select('.bg-image')
|
|
.boundingClientRect(data => {
|
|
if (!data) {
|
|
console.error('无法获取.bg-image尺寸');
|
|
return;
|
|
}
|
|
console.log('图片宽高:', data.width, data.height); // 加这个调试!
|
|
|
|
containerWidth.value = data.width * 2;
|
|
containerHeight.value = data.height * 2;
|
|
|
|
originX.value = Math.random() * (containerWidth.value - pieceSize * 2) + pieceSize;
|
|
// console.log("!!!!!",originX.value)
|
|
if (originX.value < 100) {
|
|
originX.value = 100;
|
|
}
|
|
originY.value = containerHeight.value / 2;
|
|
offsetX.value = 0;
|
|
|
|
console.log('originX:', originX.value, 'originY:', originY.value);
|
|
})
|
|
.exec();
|
|
}
|
|
|
|
function onStart(e) {
|
|
dragging.value = true;
|
|
startX.value = e.touches ? e.touches[0].clientX * 2 : e.clientX * 2;
|
|
window.addEventListener('mousemove', onMove);
|
|
window.addEventListener('mouseup', onEnd);
|
|
window.addEventListener('touchmove', onMove);
|
|
window.addEventListener('touchend', onEnd);
|
|
}
|
|
|
|
function onMove(e) {
|
|
if (!dragging.value) return;
|
|
const clientX = e.touches ? e.touches[0].clientX * 2 : e.clientX * 2;
|
|
let dx = clientX - startX.value;
|
|
dx = Math.max(0, Math.min(dx, containerWidth.value - pieceSize));
|
|
offsetX.value = dx;
|
|
}
|
|
const uToast = ref(null)
|
|
|
|
|
|
function onEnd() {
|
|
dragging.value = false;
|
|
window.removeEventListener('mousemove', onMove);
|
|
window.removeEventListener('mouseup', onEnd);
|
|
window.removeEventListener('touchmove', onMove);
|
|
window.removeEventListener('touchend', onEnd);
|
|
if (Math.abs(offsetX.value - originX.value) < tolerance) {
|
|
// console.log('验证成功');
|
|
|
|
uni.showToast({
|
|
title: '验证成功',
|
|
icon: 'none', // 不显示图标(提示信息)
|
|
duration: 2000 // 显示时长(毫秒)
|
|
})
|
|
emit('success')
|
|
} else {
|
|
offsetX.value = 0;
|
|
uni.showToast({
|
|
title: '验证失败',
|
|
icon: 'none', // 不显示图标(提示信息)
|
|
duration: 2000 // 显示时长(毫秒)
|
|
})
|
|
}
|
|
}
|
|
|
|
const bgImage = ref("");
|
|
onLoad(() => {
|
|
let randomInt = Math.floor(Math.random() * 4);
|
|
const bgImageMap = [img0, img1, img2, img3];
|
|
bgImage.value = bgImageMap[randomInt];
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('mousemove', onMove);
|
|
window.removeEventListener('mouseup', onEnd);
|
|
window.removeEventListener('touchmove', onMove);
|
|
window.removeEventListener('touchend', onEnd);
|
|
});
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
.captcha-container {
|
|
/* margin: 20rpx auto; */
|
|
user-select: none;
|
|
background-color: #fff;
|
|
width: 600rpx;
|
|
height: 700rpx;
|
|
margin: 0 auto;
|
|
z-index: 999;
|
|
border-radius: 30rpx;
|
|
overflow: hidden;
|
|
padding: 0 30rpx;
|
|
}
|
|
|
|
.captcha-image {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bg-image {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: block;
|
|
/* margin-top: 30rpx; */
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 100;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hole,
|
|
.piece {
|
|
position: absolute;
|
|
z-index: 101;
|
|
/* border-radius: 5rpx; */
|
|
box-shadow: 0 0 6rpx rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.hole {
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.piece {
|
|
background-repeat: no-repeat;
|
|
background-size: cover;
|
|
cursor: grab;
|
|
}
|
|
|
|
.slider-bar {
|
|
position: relative;
|
|
height: 100rpx;
|
|
margin-top: 0rpx;
|
|
background: rgb(245, 246, 252);
|
|
/* border-radius: 80rpx; */
|
|
width: 540rpx;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.slider-bar-font {
|
|
// z-index: -1;
|
|
color: rgb(169, 169, 171);
|
|
font-size: 28rpx;
|
|
}
|
|
}
|
|
|
|
.slider-button {
|
|
position: absolute;
|
|
top: 0rpx;
|
|
left: 0;
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
background: #fff;
|
|
/* border-radius: 60rpx; */
|
|
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.2);
|
|
transition: background 0.3s;
|
|
user-select: none;
|
|
border-radius: 10rpx;
|
|
border: 3rpx solid rgb(139, 218, 202);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.slider-button.success {
|
|
background: #4caf50;
|
|
}
|
|
|
|
.font-title {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100rpx;
|
|
font-size: 35rpx;
|
|
font-weight: 700;
|
|
}
|
|
</style> |