liveh5-nuxt/app/components/floating2/index.vue

238 lines
6.5 KiB
Vue
Raw Normal View History

<template>
<div ref="floatPanel" :style="panelStyle" class="float-panel"
@mousedown="handleMouseDown"
@touchstart="handleTouchStart">
<slot></slot>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
const props = defineProps({
position: {
type: Object,
default: () => ({})
},
fixedX: Boolean,
fixedY: Boolean,
snapEdge: {
type: Boolean,
default: false
}
});
const floatPanel = ref(null);
const panelPos = ref({ x: 0, y: 0 });
const panelSize = ref({ width: 0, height: 0 });
const windowSize = ref({ width: 0, height: 0 });
const dragOffset = ref({ x: 0, y: 0 });
const wasDragged = ref(false);
// 更新面板和窗口尺寸信息
const updateSizes = () => {
if (floatPanel.value) {
panelSize.value = {
width: floatPanel.value.offsetWidth,
height: floatPanel.value.offsetHeight
};
}
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
};
};
// 初始位置计算
const initPosition = () => {
updateSizes();
const { left, right, top, bottom } = props.position;
if (right !== undefined) {
panelPos.value.x = windowSize.value.width - parseInt(right || 0) - panelSize.value.width;
} else if (left !== undefined) {
panelPos.value.x = parseInt(left || 0);
}
if (bottom !== undefined) {
panelPos.value.y = windowSize.value.height - parseInt(bottom || 0) - panelSize.value.height;
} else if (top !== undefined) {
panelPos.value.y = parseInt(top || 0);
}
};
// 面板样式
const panelStyle = computed(() => {
return {
left: `${panelPos.value.x}px`,
top: `${panelPos.value.y}px`
};
});
// 处理鼠标按下事件
const handleMouseDown = (event) => {
// 只有点击浮动面板本身时才启动拖拽
if (event.target === floatPanel.value || floatPanel.value.contains(event.target)) {
// 判断是否是面板自身,如果是才阻止默认行为
if (event.target === floatPanel.value) {
event.preventDefault();
}
wasDragged.value = false;
const startX = event.clientX;
const startY = event.clientY;
// 计算点击位置与面板左上角的偏移
dragOffset.value = {
x: startX - panelPos.value.x,
y: startY - panelPos.value.y
};
const handleMouseMove = (e) => {
wasDragged.value = true;
const newX = e.clientX - dragOffset.value.x;
const newY = e.clientY - dragOffset.value.y;
// 应用约束
if (!props.fixedX) {
panelPos.value.x = Math.max(0, Math.min(newX, windowSize.value.width - panelSize.value.width));
}
if (!props.fixedY) {
panelPos.value.y = Math.max(0, Math.min(newY, windowSize.value.height - panelSize.value.height));
}
};
const handleMouseUp = (e) => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
if (wasDragged.value && props.snapEdge) {
snapToEdge();
}
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
};
// 处理触摸开始事件
const handleTouchStart = (event) => {
// 只有触摸浮动面板本身时才启动拖拽
if (event.target === floatPanel.value || floatPanel.value.contains(event.target)) {
// 判断是否是面板自身,如果是才阻止默认行为
if (event.target === floatPanel.value) {
event.preventDefault();
}
wasDragged.value = false;
const touch = event.touches[0];
const startX = touch.clientX;
const startY = touch.clientY;
// 计算点击位置与面板左上角的偏移
dragOffset.value = {
x: startX - panelPos.value.x,
y: startY - panelPos.value.y
};
const handleTouchMove = (e) => {
e.preventDefault();
wasDragged.value = true;
const touch = e.touches[0];
const newX = touch.clientX - dragOffset.value.x;
const newY = touch.clientY - dragOffset.value.y;
// 应用约束
if (!props.fixedX) {
panelPos.value.x = Math.max(0, Math.min(newX, windowSize.value.width - panelSize.value.width));
}
if (!props.fixedY) {
panelPos.value.y = Math.max(0, Math.min(newY, windowSize.value.height - panelSize.value.height));
}
};
const handleTouchEnd = (e) => {
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
if (wasDragged.value && props.snapEdge) {
snapToEdge();
}
};
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd);
}
};
// 执行吸附
const snapToEdge = () => {
const centerX = windowSize.value.width / 2;
const panelCenterX = panelPos.value.x + panelSize.value.width / 2;
if (panelCenterX < centerX) {
panelPos.value.x = 0; // 吸附到左边
console.log('吸附到左边边缘');
} else {
panelPos.value.x = windowSize.value.width - panelSize.value.width; // 吸附到右边
console.log('吸附到右边边缘');
}
};
// 窗口大小变化处理
const handleResize = () => {
const oldWidth = windowSize.value.width;
const oldHeight = windowSize.value.height;
updateSizes();
// 保持相对右边和底部的距离
if (props.position.right !== undefined) {
const rightDistance = oldWidth - (panelPos.value.x + panelSize.value.width);
panelPos.value.x = windowSize.value.width - rightDistance - panelSize.value.width;
}
if (props.position.bottom !== undefined) {
const bottomDistance = oldHeight - (panelPos.value.y + panelSize.value.height);
panelPos.value.y = windowSize.value.height - bottomDistance - panelSize.value.height;
}
// 确保不超出边界
panelPos.value.x = Math.max(0, Math.min(panelPos.value.x, windowSize.value.width - panelSize.value.width));
panelPos.value.y = Math.max(0, Math.min(panelPos.value.y, windowSize.value.height - panelSize.value.height));
};
onMounted(() => {
console.log('组件挂载, snapEdge =', props.snapEdge);
updateSizes();
initPosition();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
// 暴露方法
defineExpose({
snapToEdge
});
</script>
<style scoped>
.float-panel {
position: fixed;
z-index: 1000;
cursor: move;
user-select: none;
touch-action: none;
transition: left 0.2s ease-out;
}
</style>