130 lines
3.9 KiB
Vue
130 lines
3.9 KiB
Vue
|
<template>
|
|||
|
<!-- 使用 view 作为悬浮球容器,通过绑定 style 进行定位 -->
|
|||
|
<view class="floating-ball" v-show="isShow"
|
|||
|
:style="{ left: ballLeft + 'px', top: ballTop + 'px' }"
|
|||
|
@touchstart="handleTouchStart"
|
|||
|
@touchmove="handleTouchMove"
|
|||
|
@touchend="handleTouchEnd"
|
|||
|
@touchcancel="handleTouchEnd">
|
|||
|
<image class="floating-ball-img" src="/static/index/caigouqingdan.png" />
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup>
|
|||
|
import { ref, onMounted,defineEmits } from 'vue';
|
|||
|
const props = defineProps({
|
|||
|
isShow: {
|
|||
|
type: Boolean,
|
|||
|
required: true,
|
|||
|
},
|
|||
|
});
|
|||
|
const emit = defineEmits(['clickBall'])
|
|||
|
// 定义悬浮球尺寸和长按阈值
|
|||
|
const ballWidth = 60; // 悬浮球宽度,单位 px,与 CSS 中保持一致
|
|||
|
const ballHeight = 60; // 悬浮球高度
|
|||
|
const longPressThreshold = 300; // 长按时间阈值(毫秒)
|
|||
|
|
|||
|
// 悬浮球当前位置
|
|||
|
const ballLeft = ref(1090);
|
|||
|
const ballTop = ref(120);
|
|||
|
|
|||
|
// 用于判断是否进入拖动状态
|
|||
|
const isDragging = ref(false);
|
|||
|
|
|||
|
// 记录初始按下时坐标以及悬浮球起始位置
|
|||
|
let startTouchX = 0;
|
|||
|
let startTouchY = 0;
|
|||
|
let initialLeft = 0;
|
|||
|
let initialTop = 0;
|
|||
|
let longPressTimer = null;
|
|||
|
|
|||
|
// 屏幕尺寸
|
|||
|
let windowWidth = 0;
|
|||
|
let windowHeight = 0;
|
|||
|
|
|||
|
onMounted(() => {
|
|||
|
// 获取系统信息,初始化屏幕宽高(uni-app API)
|
|||
|
const res = uni.getSystemInfoSync();
|
|||
|
windowWidth = res.windowWidth;
|
|||
|
windowHeight = res.windowHeight;
|
|||
|
});
|
|||
|
|
|||
|
// 触摸开始:记录初始位置,同时启动长按定时器
|
|||
|
function handleTouchStart(e) {
|
|||
|
const touch = e.touches[0];
|
|||
|
startTouchX = touch.clientX;
|
|||
|
startTouchY = touch.clientY;
|
|||
|
initialLeft = ballLeft.value;
|
|||
|
initialTop = ballTop.value;
|
|||
|
|
|||
|
// 设置定时器,达到长按后进入拖动状态
|
|||
|
longPressTimer = setTimeout(() => {
|
|||
|
isDragging.value = true;
|
|||
|
}, longPressThreshold);
|
|||
|
}
|
|||
|
|
|||
|
// 触摸移动:如果进入拖动状态,则计算新位置,同时限制悬浮球不超出屏幕边界
|
|||
|
function handleTouchMove(e) {
|
|||
|
// 如果尚未进入拖动状态,且手指移动距离较大,则可以提前进入拖动模式
|
|||
|
if (!isDragging.value) {
|
|||
|
const touch = e.touches[0];
|
|||
|
const deltaX = Math.abs(touch.clientX - startTouchX);
|
|||
|
const deltaY = Math.abs(touch.clientY - startTouchY);
|
|||
|
if(deltaX > 5 || deltaY > 5){
|
|||
|
clearTimeout(longPressTimer);
|
|||
|
isDragging.value = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 正在拖动,更新悬浮球位置
|
|||
|
if(isDragging.value){
|
|||
|
const touch = e.touches[0];
|
|||
|
let newLeft = initialLeft + (touch.clientX - startTouchX);
|
|||
|
let newTop = initialTop + (touch.clientY - startTouchY);
|
|||
|
// 限制左右边界:不让超出屏幕(计算时需要考虑悬浮球自身尺寸)
|
|||
|
newLeft = Math.max(0, Math.min(newLeft, windowWidth - ballWidth));
|
|||
|
// 限制上下边界
|
|||
|
newTop = Math.max(0, Math.min(newTop, windowHeight - ballHeight));
|
|||
|
|
|||
|
ballLeft.value = newLeft;
|
|||
|
ballTop.value = newTop;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 触摸结束或取消:若未进入拖动状态则视为点击,触发点击处理方法;否则结束拖动状态
|
|||
|
function handleTouchEnd(e) {
|
|||
|
clearTimeout(longPressTimer);
|
|||
|
if(isDragging.value){
|
|||
|
// 拖动结束后重置状态
|
|||
|
isDragging.value = false;
|
|||
|
} else {
|
|||
|
// 非拖动状态下,触发点击事件
|
|||
|
triggerClick();
|
|||
|
}
|
|||
|
}
|
|||
|
// 点击事件处理方法
|
|||
|
function triggerClick() {
|
|||
|
emit('clickBall')
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="less" scoped>
|
|||
|
.floating-ball {
|
|||
|
position: fixed;
|
|||
|
width: 140rpx;
|
|||
|
height: 140rpx;
|
|||
|
border-radius: 50%;
|
|||
|
background: linear-gradient(to bottom right,#3FBBFE,#A541FF);
|
|||
|
border: 2rpx solid #fff;
|
|||
|
z-index: 999;
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
align-items: center;
|
|||
|
/* 可根据需要添加阴影或其他样式 */
|
|||
|
.floating-ball-img{
|
|||
|
width: 70rpx;
|
|||
|
height: 70rpx;
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|