You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
2.1 KiB
95 lines
2.1 KiB
<!-- FloatingBall.vue -->
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
|
|
const emit = defineEmits(['click'])
|
|
|
|
const x = ref(window.innerWidth - 60)
|
|
const y = ref(window.innerHeight - 150)
|
|
const isDragging = ref(false)
|
|
|
|
const ballStyle = computed(() => ({
|
|
position: 'fixed',
|
|
left: `${x.value}px`,
|
|
top: `${y.value}px`,
|
|
zIndex: 99,
|
|
userSelect: 'none',
|
|
cursor: isDragging.value ? 'grabbing' : 'grab'
|
|
}))
|
|
|
|
const getPoint = e => (e.touches ? e.touches[0] : e)
|
|
|
|
let startX = 0, startY = 0, initX = 0, initY = 0
|
|
const MOVE_THRESHOLD = 5
|
|
|
|
function startDrag(e) {
|
|
const point = getPoint(e)
|
|
startX = point.clientX
|
|
startY = point.clientY
|
|
initX = x.value
|
|
initY = y.value
|
|
isDragging.value = false
|
|
|
|
window.addEventListener('mousemove', onMove, { passive: true })
|
|
window.addEventListener('mouseup', onUp)
|
|
window.addEventListener('touchmove', onMove, { passive: true })
|
|
window.addEventListener('touchend', onUp)
|
|
|
|
e.preventDefault() // 阻止默认行为(如滚动)
|
|
}
|
|
|
|
function onMove(e) {
|
|
const p = getPoint(e)
|
|
const dx = p.clientX - startX
|
|
const dy = p.clientY - startY
|
|
|
|
if (!isDragging.value && (Math.abs(dx) > MOVE_THRESHOLD || Math.abs(dy) > MOVE_THRESHOLD)) {
|
|
isDragging.value = true
|
|
}
|
|
|
|
if (isDragging.value) {
|
|
x.value = Math.max(0, Math.min(window.innerWidth - 50, initX + dx))
|
|
y.value = Math.max(0, Math.min(window.innerHeight - 50, initY + dy))
|
|
}
|
|
}
|
|
|
|
function onUp() {
|
|
window.removeEventListener('mousemove', onMove)
|
|
window.removeEventListener('mouseup', onUp)
|
|
window.removeEventListener('touchmove', onMove)
|
|
window.removeEventListener('touchend', onUp)
|
|
|
|
if (!isDragging.value) {
|
|
emit('click')
|
|
}
|
|
|
|
isDragging.value = false
|
|
}
|
|
</script>
|
|
|
|
|
|
<template>
|
|
<div
|
|
class="float-ball"
|
|
:style="ballStyle"
|
|
@mousedown="startDrag"
|
|
@touchstart="startDrag"
|
|
@click.stop
|
|
>
|
|
<slot>AI</slot>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.float-ball {
|
|
width: 50px;
|
|
height: 50px;
|
|
border-radius: 50%;
|
|
background: #409eff;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
}
|
|
</style>
|