hldy_app/component/public/newgame/joysticknew.vue

383 lines
8.9 KiB
Vue
Raw Normal View History

2025-04-28 17:33:10 +08:00
<template>
2025-08-13 17:19:40 +08:00
<view class="move-circle" :style="{ bottom: `${movebottom}rpx`, left: `${moveleft}rpx` }" @touchmove="onTouchMove"
@touchend="onPressEnd" @touchcancel="onPressEnd">
<!-- 长按持续波纹动画 -->
<view v-if="showShadow && pao" class="light-shadow ripple-loop" :style="{
left: shadow.x + 'px',
top: shadow.y + 'px',
transform: 'translate(-50%, -50%)'
}"></view>
<!-- 点击单次波纹动画 key 刷新 -->
<view v-if="showRippleOnce && pao" :key="rippleKey" class="light-shadow ripple-once" :style="{
left: shadow.x + 'px',
top: shadow.y + 'px',
transform: 'translate(-50%, -50%)'
}" @animationend="onRippleAnimationEnd"></view>
<image :src="!notext?`/static/index/newruler/direction_1.png`: `/static/index/newruler/suere.png`" v-show="type==-1 || type==4" class="move-circle-all" />
<!-- <image src="/static/index/newruler/direction_2.png" v-show="type==-2" class="move-circle-all" />
<image src="/static/index/newruler/direction_3.png" v-show="type==-3" class="move-circle-all" /> -->
<image :src="!notext?`/static/index/newruler/direction_3.png`: `/static/index/newruler/sure_2.png`" v-show="type==3" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_5.png`: `/static/index/newruler/sure_4.png`" v-show="type==2" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_4.png`: `/static/index/newruler/sure_3.png`" v-show="type==0" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_2.png`: `/static/index/newruler/sure_1.png`" v-show="type==1" class="move-circle-all" />
<view class="pulse-circle" v-if="getblue" :key="pulseKey">
2025-04-28 17:33:10 +08:00
</view>
2025-08-13 17:19:40 +08:00
<!-- 四个方向按钮 -->
<view class="click-box-top" @tap="onTap(0, $event)" @longpress="(e) => onLongPressStart(0, e)" />
<view class="click-box-left" @tap="onTap(3, $event)" @longpress="(e) => onLongPressStart(3, e)" />
<view class="click-box-bottom" @tap="onTap(2, $event)" @longpress="(e) => onLongPressStart(2, e)" />
<view class="click-box-right" @tap="onTap(1, $event)" @longpress="(e) => onLongPressStart(1, e)" />
<view class="click-box-center" @tap="onTap(4, $event)" @longpress="(e) => onLongPressStart(4, e)" />
2025-04-28 17:33:10 +08:00
</view>
</template>
<script setup lang="ts">
2025-08-13 17:19:40 +08:00
import { ref, reactive, watch, nextTick, onBeforeUnmount } from 'vue'
const emit = defineEmits<{
(e : 'movecard', dir : number) : void
}>()
const key = ref(-1)
let clickResetTimer : ReturnType<typeof setTimeout> | null = null
let longPressInterval : ReturnType<typeof setInterval> | null = null
let isLongPress = false
const type = ref(-1)
const shadow = reactive({ x: 0, y: 0 })
const showShadow = ref(false) // 长按持续波纹
const showRippleOnce = ref(false) // 点击单次波纹
const rippleKey = ref(0) // 动画 key用来强制刷新
const RADIUS = uni.upx2px(175)
const DIAMETER = uni.upx2px(350)
const LEFT = uni.upx2px(-50)
const BOTTOM = uni.upx2px(100)
const windowHeight = uni.getSystemInfoSync().windowHeight
const TOP = windowHeight - BOTTOM - DIAMETER
const props = defineProps({
getblue: {
type: Boolean,
default: false
},
movebottom: {
type: Number,
default: 0
},
moveleft: {
type: Number,
default: 0
},
pao:{
type: Boolean,
default: true
},
notext:{
type: Boolean,
default: false
}
});
const getblue = ref(false)
const pulseKey = ref(0)
let timeout1 : ReturnType<typeof setTimeout> | null = null
let timeout2 : ReturnType<typeof setTimeout> | null = null
let timeout3 : ReturnType<typeof setTimeout> | null = null
const types = [-1, -2, -3]
const index = ref(0)
let timer = null;
const switchType = () => {
index.value = (index.value + 1) % types.length
type.value = types[index.value]
}
watch(() => props.getblue, (val) => {
// if (timer) clearInterval(timer)
// timer = setInterval(() => {
// switchType()
// }, 100)
// 清除上次的定时器,防止重复播放
// if (timeout1) clearTimeout(timeout1)
// if (timeout2) clearTimeout(timeout2)
// if (timeout3) clearTimeout(timeout3)
// // 重置动画状态
// getblue.value = false
// nextTick(() => {
// pulseKey.value++
// getblue.value = true
// // 第一阶段结束
// timeout1 = setTimeout(() => {
// getblue.value = false
// }, 3000)
// // 第二阶段开始
// timeout2 = setTimeout(() => {
// getblue.value = true
// }, 3010)
// // 第二阶段结束
// timeout3 = setTimeout(() => {
// getblue.value = false
// }, 6500)
// })
// }
})
onBeforeUnmount(() => {
if (timer) clearInterval(timer)
})
function onTap(dir : number, e : any) {
if (timer) clearInterval(timer)
if (isLongPress) return
clearClickTimer()
key.value = dir
emit('movecard', dir)
// console.log("?????",e.touches)
if (e?.touches && e.touches.length) {
const touch = e.touches[0]
updateShadowPosition(touch.pageX, touch.pageY)
}
showRippleOnce.value = false
rippleKey.value++
type.value = dir
setTimeout(() => {
type.value = -1
}, 300)
setTimeout(() => {
showRippleOnce.value = true
}, 16)
clickResetTimer = setTimeout(() => {
key.value = -1
clickResetTimer = null
}, 300)
}
function onRippleAnimationEnd() {
showRippleOnce.value = false
}
function onLongPressStart(dir : number, e : TouchEvent) {
if (timer) clearInterval(timer)
clearLongPressInterval()
isLongPress = true
showShadow.value = true
type.value = dir
const touch = (e?.touches || [])[0]
if (touch) updateShadowPosition(touch.pageX, touch.pageY)
key.value = dir
emit('movecard', dir)
longPressInterval = setInterval(() => {
key.value = dir
emit('movecard', dir)
}, 500)
}
function onPressEnd() {
clearClickTimer()
clearLongPressInterval()
isLongPress = false
showShadow.value = false
key.value = -1;
type.value = -1
}
function clearClickTimer() {
if (clickResetTimer) {
clearTimeout(clickResetTimer)
clickResetTimer = null
}
}
function clearLongPressInterval() {
if (longPressInterval) {
clearInterval(longPressInterval)
longPressInterval = null
}
}
2025-04-28 17:33:10 +08:00
2025-08-13 17:19:40 +08:00
function updateShadowPosition(pageX : number, pageY : number) {
let x = pageX
let y = pageY - TOP - 50 + props.movebottom/2
2025-04-28 17:33:10 +08:00
2025-08-13 17:19:40 +08:00
const dx = x - RADIUS
const dy = y - RADIUS
const dist = Math.sqrt(dx * dx + dy * dy)
if (dist > RADIUS) {
const angle = Math.atan2(dy, dx)
x = RADIUS + RADIUS * Math.cos(angle)
y = RADIUS + RADIUS * Math.sin(angle)
}
shadow.x = x
shadow.y = y
}
function onTouchMove(e : any) {
if (!isLongPress) return
const touch = (e.detail?.touches || e.touches || [])[0]
if (!touch) return
updateShadowPosition(touch.pageX, touch.pageY)
}
</script>
2025-04-28 17:33:10 +08:00
<style lang="less" scoped>
.move-circle {
position: absolute;
2025-08-13 17:19:40 +08:00
bottom: 0rpx;
left: 0rpx;
width: 350rpx;
height: 350rpx;
2025-04-28 17:33:10 +08:00
display: flex;
justify-content: center;
align-items: center;
2025-08-13 17:19:40 +08:00
z-index: 99;
touch-action: none;
.click-box-top {
2025-04-28 17:33:10 +08:00
position: absolute;
2025-08-13 17:19:40 +08:00
top: 20rpx;
2025-04-28 17:33:10 +08:00
left: 70rpx;
2025-08-13 17:19:40 +08:00
width: 220rpx;
height: 80rpx;
2025-04-28 17:33:10 +08:00
}
2025-08-13 17:19:40 +08:00
.click-box-bottom {
2025-04-28 17:33:10 +08:00
position: absolute;
2025-08-13 17:19:40 +08:00
bottom: 20rpx;
2025-04-28 17:33:10 +08:00
left: 70rpx;
2025-08-13 17:19:40 +08:00
width: 220rpx;
height: 80rpx;
2025-04-28 17:33:10 +08:00
}
2025-08-13 17:19:40 +08:00
.click-box-left {
2025-04-28 17:33:10 +08:00
position: absolute;
2025-08-13 17:19:40 +08:00
bottom: 100rpx;
2025-04-28 17:33:10 +08:00
left: 0;
2025-08-13 17:19:40 +08:00
width: 90rpx;
height: 150rpx;
2025-04-28 17:33:10 +08:00
}
2025-08-13 17:19:40 +08:00
.click-box-right {
2025-04-28 17:33:10 +08:00
position: absolute;
2025-08-13 17:19:40 +08:00
bottom: 100rpx;
2025-04-28 17:33:10 +08:00
right: 0;
2025-08-13 17:19:40 +08:00
width: 90rpx;
height: 150rpx;
}
.click-box-center {
position: absolute;
bottom: 130rpx;
right: 130rpx;
width: 90rpx;
height: 90rpx;
// background-color: red;
}
}
.move-circle-all {
width: 350rpx;
height: 350rpx;
}
.light-shadow {
position: absolute;
width: 40rpx;
height: 40rpx;
background-color: transparent;
border: 60rpx solid #3da6ff;
border-radius: 50%;
pointer-events: none;
opacity: 1;
}
/* 无限循环波纹动画,长按时用 */
.ripple-loop {
animation: rippleLoop 1.2s ease-out infinite;
}
/* 点击一次的波纹动画 */
.ripple-once {
animation: rippleLoop 1.2s ease-out forwards;
}
@keyframes rippleLoop {
0% {
transform: translate(-50%, -50%) scale(0.5);
opacity: 0.6;
}
100% {
transform: translate(-50%, -50%) scale(2.5);
opacity: 0;
2025-04-28 17:33:10 +08:00
}
}
2025-08-13 17:19:40 +08:00
.light-circle {
position: relative;
width: 150px;
height: 150px;
border-radius: 50%;
background: #111;
/* 你背景色自己改 */
overflow: visible;
}
.circle {
position: relative;
width: 150px;
height: 150px;
border-radius: 50%;
background: #222;
margin: 50px;
}
.pulse-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
background: radial-gradient(circle, #03a4ff 0%, transparent 70%);
animation: pulse 3s forwards;
}
@keyframes pulse {
0% {
width: 0;
height: 0;
opacity: 0.8;
}
50% {
width: 350rpx;
height: 350rpx;
opacity: 0.4;
}
100% {
width: 0;
height: 0;
opacity: 0;
}
2025-04-28 17:33:10 +08:00
}
</style>