227 lines
5.8 KiB
Vue
227 lines
5.8 KiB
Vue
<template>
|
||
<view class="move-circle" :style="{ bottom: `${movebottom}rpx`, left: `${moveleft}rpx` }" @touchend="onLongPressEnd"
|
||
@touchcancel="onLongPressEnd">
|
||
|
||
<!-- 返回 -->
|
||
<view :class="beblue === 5 ? 'click-box-target' : 'click-box'" @tap="onTap(5)"
|
||
@longpress="(e) => onLongPressStart(5, e)">
|
||
<span :class="beblue === 5 ? 'grad-text' : ''">返回</span>
|
||
</view>
|
||
|
||
<!-- 上 -->
|
||
<view :class="beblue === 0 ? 'click-box-target' : 'click-box'" @tap="onTap(0)"
|
||
@longpress="(e) => onLongPressStart(0, e)">
|
||
<image :src="`/static/index/newruler/arrow_1${beblue===0 ? '_1' : ''}.png`" class="image-photo" />
|
||
</view>
|
||
|
||
<!-- 确定 -->
|
||
<view :class="beblue === 4 ? 'click-box-target' : 'click-box'" @tap="onTap(4)"
|
||
@longpress="(e) => onLongPressStart(4, e)">
|
||
<span :class="beblue === 4 ? 'grad-text' : ''">确定</span>
|
||
</view>
|
||
|
||
<!-- 右 -->
|
||
<view :class="beblue === 3 ? 'click-box-target' : 'click-box'" @tap="onTap(3)"
|
||
@longpress="(e) => onLongPressStart(3, e)">
|
||
<image :src="`/static/index/newruler/arrow_3${beblue===3 ? '_3' : ''}.png`" class="image-photo" />
|
||
</view>
|
||
|
||
<!-- 下 -->
|
||
<view :class="beblue === 2 ? 'click-box-target' : 'click-box'" @tap="onTap(2)"
|
||
@longpress="(e) => onLongPressStart(2, e)">
|
||
<image :src="`/static/index/newruler/arrow_2${beblue===2 ? '_2' : ''}.png`" class="image-photo" />
|
||
</view>
|
||
|
||
<!-- 左 -->
|
||
<view :class="beblue === 1 ? 'click-box-target' : 'click-box'" @tap="onTap(1)"
|
||
@longpress="(e) => onLongPressStart(1, e)">
|
||
<image :src="`/static/index/newruler/arrow_4${beblue===1 ? '_4' : ''}.png`" class="image-photo" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onBeforeUnmount } from 'vue'
|
||
|
||
const emit = defineEmits<{ (e : 'movecard', dir : number) : void }>()
|
||
|
||
let clickResetTimer : ReturnType<typeof setTimeout> | null = null
|
||
let longPressInterval : ReturnType<typeof setInterval> | null = null
|
||
let isLongPress = false
|
||
const beblue = ref<number>(-1)
|
||
let activeLongPressDir : number | null = null
|
||
|
||
const props = defineProps({
|
||
movebottom: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
moveleft: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
})
|
||
|
||
function clearClickResetTimer() {
|
||
if (clickResetTimer) {
|
||
clearTimeout(clickResetTimer)
|
||
clickResetTimer = null
|
||
}
|
||
}
|
||
|
||
function clearLongPressInterval() {
|
||
if (longPressInterval) {
|
||
clearInterval(longPressInterval)
|
||
longPressInterval = null
|
||
}
|
||
isLongPress = false
|
||
activeLongPressDir = null
|
||
}
|
||
|
||
// 单击(或短按)
|
||
function onTap(dir : number) {
|
||
// 立刻触发一次
|
||
clearLongPressInterval()
|
||
clearClickResetTimer()
|
||
|
||
beblue.value = dir
|
||
emit('movecard', dir)
|
||
|
||
// 800ms 后恢复(如果期间有新点击会清除并重置)
|
||
clickResetTimer = setTimeout(() => {
|
||
beblue.value = -1
|
||
clickResetTimer = null
|
||
}, 500)
|
||
}
|
||
|
||
// 长按开始(由 longpress 事件触发)
|
||
function onLongPressStart(dir : number, e ?: any) {
|
||
// 先清理点击计时器,确保长按状态保持
|
||
clearClickResetTimer()
|
||
clearLongPressInterval()
|
||
|
||
beblue.value = dir
|
||
emit('movecard', dir)
|
||
|
||
// 开始每 500ms 发一次
|
||
activeLongPressDir = dir
|
||
isLongPress = true
|
||
longPressInterval = setInterval(() => {
|
||
if (activeLongPressDir !== null) emit('movecard', activeLongPressDir)
|
||
}, 500)
|
||
}
|
||
|
||
// 长按结束(touchend / touchcancel)
|
||
function onLongPressEnd() {
|
||
// 如果没有长按在进行,直接返回(防止意外触发)
|
||
if (!isLongPress) return
|
||
|
||
// 停止 interval
|
||
clearLongPressInterval()
|
||
|
||
// 0.8s 后恢复选中态
|
||
clearClickResetTimer()
|
||
clickResetTimer = setTimeout(() => {
|
||
beblue.value = -1
|
||
clickResetTimer = null
|
||
}, 500)
|
||
}
|
||
|
||
onBeforeUnmount(() => {
|
||
clearClickResetTimer()
|
||
clearLongPressInterval()
|
||
})
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.move-circle {
|
||
position: absolute;
|
||
bottom: 0rpx;
|
||
left: 0rpx;
|
||
width: 400rpx;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
z-index: 99;
|
||
touch-action: none;
|
||
}
|
||
|
||
.click-box,
|
||
.click-box-target {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
margin-left: 15rpx;
|
||
margin-bottom: 15rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 30rpx;
|
||
font-size: 28rpx;
|
||
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.25s ease;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.click-box {
|
||
background-color: #f6f6f9;
|
||
box-shadow: 0 4px 12px #e5e7ea;
|
||
border: 2rpx solid #e6e7eb;
|
||
color: #888d99;
|
||
}
|
||
|
||
/* 选中态:背景径向渐变 + 中心缩放动画(从中间放大再回到原样) */
|
||
.click-box-target {
|
||
background: radial-gradient(circle at 30% 30%, #f2f7fd 0%, #deeaf9 100%);
|
||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.08);
|
||
border: 2rpx solid #c3ccd9;
|
||
color: transparent;
|
||
/* 文字使用渐变填充 */
|
||
animation: scalePulse 360ms cubic-bezier(.2, .8, .2, 1);
|
||
transform-origin: center center;
|
||
}
|
||
|
||
@keyframes scalePulse {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
25% {
|
||
/* 先收缩一点点 */
|
||
transform: scale(0.94);
|
||
}
|
||
65% {
|
||
/* 再放大到略超出的感觉 */
|
||
transform: scale(1.08);
|
||
}
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
/* 文本渐变(用于返回/确定文字) */
|
||
.grad-text {
|
||
background-image: linear-gradient(90deg, #5b8bb3, #87a1bd);
|
||
background-size: 200% 100%;
|
||
background-position: 0% 50%;
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
transition: background-position 0.8s linear;
|
||
}
|
||
|
||
/* 选中时文字渐变滚动效果 */
|
||
.click-box-target .grad-text {
|
||
background-position: 100% 50%;
|
||
}
|
||
|
||
.image-photo {
|
||
width: 30%;
|
||
height: 30%;
|
||
transition: transform 0.18s ease, filter 0.18s ease;
|
||
}
|
||
|
||
/* 选中时图片略微放大 */
|
||
.click-box-target .image-photo {
|
||
/* 让图片跟随父元素缩放,不额外放大,保留平滑过渡 */
|
||
transform: none;
|
||
transition: transform 0.18s ease, filter 0.18s ease;
|
||
filter: none;
|
||
}
|
||
</style> |