Vue3移动端可拖拽悬浮按钮组件完整实现指南

448 阅读2分钟

一、组件核心功能

本组件实现移动端悬浮按钮的完整交互方案,包含以下特性:

  1. 自由拖拽:支持全屏范围内自由拖动
  2. 边界约束:自动限制在可视区域内
  3. 吸边效果:释放时自动吸附到屏幕边缘[
  4. 事件分离:点击事件与拖拽事件互不干扰
  5. 性能优化:采用transform实现流畅动画

二、完整组件代码

<template>
  <div class="dock-btn"
       ref="dockBtn"
       @click="handleClick"
       @touchstart="handleTouchStart"
       @touchmove="handleTouchMove"
       @touchend="handleTouchEnd">
    <slot name="icon">
      <!-- 默认图标 -->
      <svg ...></svg>
    </slot>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const emits = defineEmits(['click'])
const dockBtn = ref(null)
let isDragging = false
let startX = 0, startY = 0
let initialX = 0, initialY = 0

// 初始化定位
const initPosition = () => {
  const btn = dockBtn.value
  btn.style.transform = `translate(${window.innerWidth - 80}px, ${window.innerHeight - 200}px)`
}

// 触摸事件处理
const handleTouchStart = (e) => {
  isDragging = true
  const touch = e.touches[0]
  startX = touch.clientX
  startY = touch.clientY
  const rect = dockBtn.value.getBoundingClientRect()
  initialX = rect.left
  initialY = rect.top
}

const handleTouchMove = (e) => {
  if (!isDragging) return
  e.preventDefault()
  
  const touch = e.touches[0]
  const deltaX = touch.clientX - startX
  const deltaY = touch.clientY - startY
  
  // 计算新位置
  let newX = initialX + deltaX
  let newY = initialY + deltaY
  
  // 边界约束
  const maxX = window.innerWidth - dockBtn.value.offsetWidth
  const maxY = window.innerHeight - dockBtn.value.offsetHeight
  newX = Math.max(0, Math.min(maxX, newX))
  newY = Math.max(0, Math.min(maxY, newY))
  
  // 使用transform优化性能[7](@ref)
  dockBtn.value.style.transform = `translate(${newX}px, ${newY}px)`
}

const handleTouchEnd = () => {
  isDragging = false
  // 吸边处理[5](@ref)
  const rect = dockBtn.value.getBoundingClientRect()
  const midPoint = window.innerWidth / 2
  const newX = rect.left > midPoint 
    ? window.innerWidth - dockBtn.value.offsetWidth - 20 
    : 20
  
  dockBtn.value.style.transition = 'transform 0.3s ease'
  dockBtn.value.style.transform = `translate(${newX}px, ${rect.top}px)`
  
  setTimeout(() => {
    dockBtn.value.style.transition = ''
  }, 300)
}

const handleClick = () => {
  if (!isDragging) {
    emits('click')
  }
}

onMounted(initPosition)
onBeforeUnmount(() => {
  dockBtn.value = null
})
</script>

<style lang="scss" scoped>
.dock-btn {
  position: fixed;
  width: 60px;
  height: 60px;
  border-radius: 30px;
  background: linear-gradient(145deg, #ffffff, #e6ebee);
  box-shadow: 0 4px 12px rgba(#1678fc, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;
  transition: transform 0.2s ease;
  z-index: 9999;

  &.dragging {
    opacity: 0.9;
    transform: scale(1.1);
    box-shadow: 0 6px 16px rgba(#1678fc, 0.3);
  }
}
</style>

## 三、进阶功能扩展
### 1. 自动吸边优化
```javascript
const autoDock = (currentX) => {
  const threshold = 20
  const midPoint = window.innerWidth / 2
  return currentX > midPoint 
    ? window.innerWidth - dockBtn.value.offsetWidth - threshold 
    : threshold
}

2. 拖拽方向限制

// 垂直拖拽模式
if (props.direction === 'vertical') {
  newX = initialX // 锁定X轴位置
}

3. 多指触控处理

const handleTouchStart = (e) => {
  if (e.touches.length > 1) return // 屏蔽多指操作
  // ...原有逻辑
}

四、最佳实践建议

  1. 性能优化

    • 使用will-change: transform启用GPU加速
    • 对高频事件使用requestAnimationFrame节流
  2. 兼容性处理

    // 桌面端兼容
    const handleMouseDown = (e) => {
      startX = e.clientX
      startY = e.clientY
      // ...后续逻辑与touch事件相同
    }
    
  3. 状态管理

    // 持久化位置数据
    const savePosition = () => {
      localStorage.setItem('btnPosition', JSON.stringify({
        x: dockBtn.value.offsetLeft,
        y: dockBtn.value.offsetTop
      }))
    }
    

五、应用场景示例

场景技术方案实现要点
客服悬浮按钮基础拖拽+点击事件限制右侧吸附
游戏虚拟摇杆圆形区域限制+方向计算极坐标转换
图片裁切控件拖拽+缩放组合矩阵变换计算
看板组件布局多实例拖拽+碰撞检测使用ResizeObserver

: 实现拖拽的基础事件绑定与DOM操作 : 边界约束计算与可视区域限制 : 移动端触摸事件处理方案 : 吸边效果实现原理 : 性能优化与transform应用 : 触摸事件细节处理