Skip to content

useDraggable 拖拽钩子函数文档

一、功能概述

useDraggable 是基于 Vue 3 的响应式拖拽钩子,用于实现 DOM 元素的可拖拽功能。它通过监听鼠标事件动态计算元素位置,并支持拖拽区域边界限制,适用于模态框、浮动窗口、自定义组件等需要拖拽交互的场景。

二、核心特性

  1. 响应式拖拽控制
    通过 draggable 响应式参数动态开启/关闭拖拽功能,支持实时状态切换。

  2. 边界限制
    自动根据视口尺寸和目标元素尺寸计算拖拽范围,防止元素超出屏幕边界。

  3. 多引用绑定

    • targetRef:绑定需要拖拽的目标元素(跟随鼠标移动的元素)
    • dragRef:绑定触发拖拽的手柄元素(可选,若未传则默认目标元素自身可拖拽)
  4. 平滑定位
    使用 CSS transform: translate 实现元素定位,保证动画流畅性。

三、参数说明

参数名类型说明
targetRefRef<HTMLElement | undefined>目标元素引用(必填),需绑定实际 DOM 元素
dragRefRef<HTMLElement | undefined>拖拽手柄元素引用(可选),若未提供则默认通过目标元素自身触发拖拽
draggableComputedRef<boolean>拖拽功能开关(必填),通过计算属性动态控制是否允许拖拽

四、内部实现逻辑

1. 鼠标事件流程

Lexical error on line 2. Unrecognized text.
graph LRA[鼠标按下(dragRef 或 targetRef
--------------^

2. 边界计算规则

  • 最小左边界minLeft = -targetLeft + offsetX(防止元素左边缘超出视口左边界)
  • 最大左边界maxLeft = clientWidth - targetLeft - targetWidth + offsetX(防止元素右边缘超出视口右边界)
  • 上下边界同理,基于视口高度和元素高度计算

五、使用示例

场景 1:基础拖拽(目标元素自身可拖拽)

vue
<template>
  <div ref="targetRef" class="draggable-box">
    按住我拖动
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useDraggable } from '.@eco-library/hooks';

const targetRef = ref<HTMLElement>();
const draggable = computed(() => true); // 始终允许拖拽

useDraggable(targetRef, undefined, draggable);
</script>

<style scoped>
.draggable-box {
  position: absolute;
  width: 200px;
  height: 100px;
  background: #4A90E2;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: move;
}
</style>

场景 2:带手柄的拖拽

vue
<template>
  <div ref="targetRef" class="modal">
    <div ref="dragRef" class="header">按住头部拖动</div>
    <div class="content">模态框内容</div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useDraggable } from '.@eco-library/hooks';

const targetRef = ref<HTMLElement>(); // 模态框主体
const dragRef = ref<HTMLElement>(); // 拖拽手柄(头部)
const draggable = computed(() => true); // 开启拖拽

useDraggable(targetRef, dragRef, draggable);
</script>

场景 3:动态开关拖拽功能

vue
<template>
  <div>
    <button @click="toggleDraggable">
      {{ isDraggable ? '禁用拖拽' : '启用拖拽' }}
    </button>
    <div ref="targetRef" class="draggable-box">
      可拖拽元素(当前状态:{{ isDraggable ? '启用' : '禁用' }})
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useDraggable } from '.@eco-library/hooks';

const targetRef = ref<HTMLElement>();
const isDraggable = ref(true);
const draggable = computed(() => isDraggable.value);

useDraggable(targetRef, undefined, draggable);
</script>

六、注意事项

  1. 元素定位要求
    目标元素必须设置为 position: absoluteposition: fixed,否则拖拽功能无效。

  2. 性能优化

    • 避免在拖拽元素内部嵌套大量复杂子元素
    • 若不需要边界限制,可修改内部边界计算逻辑(注释掉 Math.min/max 部分)
  3. 移动端适配
    目前仅支持鼠标事件,若需移动端手势拖拽,可扩展支持 touchstart/touchmove/touchend 事件。

七、扩展建议

1. 支持动画过渡

typescript
// 扩展钩子,添加过渡效果
export function useDraggableWithTransition(...args) {
  const { targetRef, ...rest } = useDraggable(...args);
  watchEffect(() => {
    targetRef.value!.style.transition = 'transform 0.3s ease-out';
  });
  return { ...rest };
}

2. 记录拖拽位置

typescript
// 扩展钩子,返回当前位置坐标
export function useDraggableWithPosition(...args) {
  const { targetRef } = useDraggable(...args);
  const position = computed(() => ({
    x: parseFloat(targetRef.value!.style.transform.split(',')[0].split('(')[1]),
    y: parseFloat(targetRef.value!.style.transform.split(',')[1])
  }));
  return { position, ...rest };
}

八、依赖说明

  • 核心依赖:Vue 3.x(@vue/composition-api
  • 类型支持:需手动声明参数类型(已包含 TypeScript 类型定义)

仅供内部学习使用