feat(ebiz): 优化FAB组件拖拽功能

- 修复当移动浮窗之后,点击其他地方浮窗会出现点击位置的问题
- 完善拖拽相关数据字段注释
- 新增窗口大小更新方法
- 重构拖拽开始、进行和结束逻辑
- 添加触摸事件支持和边界限制
- 实现点击与拖拽事件的正确分离
This commit is contained in:
hz
2025-12-01 13:43:46 +08:00
parent 3bb1d67290
commit 7fa320601c

View File

@@ -30,18 +30,20 @@ export default {
data() { data() {
return { return {
isDragging: false, isDragging: false, // 是否正在拖动
hasDragged: false, // 新增:标记是否发生了拖拽 hasDragged: false, // 是否发生了拖拽
startX: 0, startX: 0, // 触摸点相对于元素左上角的X偏移
startY: 0, startY: 0, // 触摸点相对于元素左上角的Y偏移
posX: 0, posX: 0, // 元素当前位置X
posY: 0, posY: 0, // 元素当前位置Y
windowWidth: 0, initialTouchX: 0, // 初始触摸点X
windowHeight: 0, initialTouchY: 0, // 初始触摸点Y
fabWidth: 0, windowWidth: 0, // 视口宽度
fabHeight: 0, windowHeight: 0, // 视口高度
initialX: 0, fabWidth: 0, // FAB宽度
initialY: 0 fabHeight: 0, // FAB高度
initialX: 0, // 初始位置X
initialY: 0 // 初始位置Y
} }
}, },
@@ -79,6 +81,9 @@ export default {
}, },
methods: { methods: {
/**
* 更新窗口大小
*/
updateWindowSize() { updateWindowSize() {
this.windowWidth = window.innerWidth this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight this.windowHeight = window.innerHeight
@@ -90,89 +95,128 @@ export default {
} }
}, },
/**
* 开始拖动
* @param event {TouchEvent}
*/
startDrag(event) { startDrag(event) {
if (!this.draggable) return if (!this.draggable) return
// 阻止默认行为(如页面滚动)和事件冒泡
event.preventDefault()
event.stopPropagation()
const touch = event.touches[0]
// 记录初始触摸位置
this.initialTouchX = touch.clientX
this.initialTouchY = touch.clientY
// 记录元素当前位置和触摸点的偏移
this.startX = touch.clientX - this.posX
this.startY = touch.clientY - this.posY
// 设置拖动状态
this.isDragging = true this.isDragging = true
this.hasDragged = false this.hasDragged = false
const clientX = event.touches ? event.touches[0].clientX : event.clientX // 禁用文本选择
const clientY = event.touches ? event.touches[0].clientY : event.clientY
this.startX = clientX - this.posX
this.startY = clientY - this.posY
document.body.style.userSelect = 'none' document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
const options = { passive: false }
document.addEventListener('mousemove', this.onDrag, options)
document.addEventListener('touchmove', this.onDrag, options)
document.addEventListener('mouseup', this.stopDrag, options)
document.addEventListener('touchend', this.stopDrag, options)
}, },
/**
* 拖动中
* @param event { TouchEvent }
*/
onDrag(event) { onDrag(event) {
if (!this.isDragging) return if (!this.isDragging) return
this.hasDragged = true // 阻止默认行为(如页面滚动)
event.preventDefault()
event.stopPropagation()
const clientX = event.touches ? event.touches[0].clientX : event.clientX // 获取当前触摸点
const clientY = event.touches ? event.touches[0].clientY : event.clientY const touch = event.touches[0]
let newX = clientX - this.startX // 计算新位置(相对于初始触摸点的偏移)
let newY = clientY - this.startY let newX = touch.clientX - this.startX
let newY = touch.clientY - this.startY
// 应用边界限制
if (this.boundary) { if (this.boundary) {
newX = Math.max(0, Math.min(newX, this.windowWidth - this.fabWidth)) const maxX = this.windowWidth - this.fabWidth
newY = Math.max(0, Math.min(newY, this.windowHeight - this.fabHeight)) const maxY = this.windowHeight - this.fabHeight
// 确保不会移出视口
newX = Math.max(0, Math.min(newX, maxX))
newY = Math.max(0, Math.min(newY, maxY))
} }
// 使用transform实现平滑移动
this.posX = newX this.posX = newX
this.posY = newY this.posY = newY
// 标记为已拖动,防止点击事件触发
if (Math.abs(touch.clientX - this.initialTouchX) > 5 || Math.abs(touch.clientY - this.initialTouchY) > 5) {
this.hasDragged = true
}
}, },
/**
* 拖动结束
* @param event { TouchEvent }
*/
stopDrag(event) { stopDrag(event) {
if (!this.isDragging) return if (!this.isDragging) return
this.isDragging = false this.isDragging = false
document.body.style.userSelect = '' document.body.style.userSelect = ''
this.removeEventListeners()
// 如果是拖拽结束,阻止后续的点击事件 // 阻止事件冒泡,避免触发父元素的点击事件
if (this.hasDragged) { if (event) {
// 延迟重置标志位,确保不会触发点击事件 event.stopPropagation()
setTimeout(() => { event.preventDefault()
this.hasDragged = false
}, 100)
} }
this.removeEventListeners()
this.$emit('drag-end', { x: this.posX, y: this.posY }) this.$emit('drag-end', { x: this.posX, y: this.posY })
}, },
/**
* 点击事件
* @param event { TouchEvent }
*/
handleClick(event) { handleClick(event) {
// 如果是拖拽操作,不触发点击事件 // 如果是拖拽操作,不触发点击事件
if (this.isDragging || this.hasDragged) { if (this.hasDragged) {
event.stopPropagation() event.stopPropagation()
event.preventDefault()
this.hasDragged = false this.hasDragged = false
return return false
} }
this.$emit('click', event) this.$emit('click', event)
return true
}, },
/**
* 移除事件监听器
*/
removeEventListeners() { removeEventListeners() {
const options = { passive: false } // 恢复用户选择
document.removeEventListener('mousemove', this.onDrag, options) document.body.style.userSelect = ''
document.removeEventListener('touchmove', this.onDrag, options) document.body.style.webkitUserSelect = ''
document.removeEventListener('mouseup', this.stopDrag, options)
document.removeEventListener('touchend', this.stopDrag, options) // 重置拖动状态
this.isDragging = false
} }
} }
} }
</script> </script>
<template> <template>
<div ref="fab" :style="fabStyle" class="fab-container" @mousedown="startDrag" @touchstart="startDrag"> <div ref="fab" :style="fabStyle" class="fab-container" @touchend="handleClick" @touchmove="onDrag" @touchstart="startDrag">
<div class="fab-content" @click="handleClick" @touchend="handleClick"> <div class="fab-content">
<slot> <slot>
<div class="fab-default"> <div class="fab-default">
<i v-if="icon" :class="icon"></i> <i v-if="icon" :class="icon"></i>
@@ -188,9 +232,10 @@ $base: #e9332e;
.fab-container { .fab-container {
position: fixed; position: fixed;
cursor: move;
user-select: none; user-select: none;
-webkit-user-select: none;
touch-action: none; touch-action: none;
-webkit-tap-highlight-color: transparent;
} }
.fab-content { .fab-content {