当前位置:首页 > 技术 > 正文内容

Vue 3 自定义指令实战:实现可复用的 DOM 拖拽功能

访客 技术 2026年6月16日 1

Vue 3 自定义指令封装 DOM 拖拽功能

在 Vue 3 开发中,对于跨组件复用的纯逻辑函数,通常将其提取到独立的 TS/JS 模块中。然而,当需求涉及直接操作 DOM 元素(如添加拖拽、聚焦、权限控制等行为)时,使用自定义指令(Custom Directives)是更优雅且符合 Vue 数据驱动理念的方案。

本文将以实现一个通用的 DOM 拖拽指令为例,讲解如何注册自定义指令,并探讨在原生 HTML 元素与第三方 UI 组件库中的不同应用策略。

1. 全局注册拖拽指令

在应用入口文件(如 main.ts)中全局注册指令。为了保证事件绑定的安全性,推荐使用 addEventListener 替代直接赋值,并增加对目标元素定位属性的自动处理。

import { createApp, DirectiveBinding } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.directive('draggable', {
  mounted(element: HTMLElement, binding: DirectiveBinding) {
    // 支持通过 binding.value 传入目标元素的选择器,默认为当前元素
    const targetSelector = binding.value;
    const targetEl = targetSelector 
      ? document.querySelector(targetSelector) as HTMLElement 
      : element;
      
    if (!targetEl) return;

    // 确保目标元素具备定位属性
    const position = window.getComputedStyle(targetEl).position;
    if (position === 'static') {
      targetEl.style.position = 'relative';
    }

    let startX = 0;
    let startY = 0;
    let initialLeft = 0;
    let initialTop = 0;

    const handleMouseMove = (event: MouseEvent) => {
      const deltaX = event.clientX - startX;
      const deltaY = event.clientY - startY;
      
      targetEl.style.left = `${initialLeft + deltaX}px`;
      targetEl.style.top = `${initialTop + deltaY}px`;
    };

    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };

    const handleMouseDown = (event: MouseEvent) => {
      event.preventDefault();
      startX = event.clientX;
      startY = event.clientY;
      initialLeft = targetEl.offsetLeft;
      initialTop = targetEl.offsetTop;

      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
    };

    // 将触发事件绑定在指令所在的元素上(作为拖拽手柄)
    element.addEventListener('mousedown', handleMouseDown);
  }
});

app.mount('#app');

2. 在基础 HTML 元素中使用

对于普通的 DOM 节点,只需在元素上添加 v-draggable 指令即可使其具备拖拽能力。

<template>
  <!-- 默认拖拽自身 -->
  <div class="drag-box" v-draggable>
    拖拽此区域
  </div>

  <!-- 图片元素同样适用 -->
  <img 
    src="/logo.png" 
    alt="Logo" 
    class="drag-image" 
    v-draggable 
  />
</template>

<style scoped>
.drag-box {
  width: 150px;
  height: 150px;
  background-color: #409eff;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: move;
}
.drag-image {
  width: 100px;
  cursor: move;
}
</style>

3. 适配第三方 UI 组件库

在使用 Ant Design Vue 或 Element Plus 等 UI 组件库时,组件内部通常存在多层 DOM 嵌套。如果直接对组件内部的某个子元素使用指令,可能会导致拖拽错位。此时,可以利用指令的 binding.value 传递 CSS 选择器,将"拖拽手柄"与"实际移动的目标容器"分离。

以下以 Ant Design Vue 的 Modal 弹窗为例,将弹窗的标题栏作为拖拽手柄,移动整个弹窗内容区:

<template>
  <a-button type="primary" @click="isVisible = true">
    打开可拖拽弹窗
  </a-button>

  <a-modal 
    v-model:open="isVisible" 
    :footer="null"
    :closable="false"
    class="draggable-modal"
  >
    <!-- 使用插槽自定义标题,并传入弹窗内容区的类名作为拖拽目标 -->
    <template #title>
      <div v-draggable="'.ant-modal-content'" style="cursor: move;">
        按住此处拖拽弹窗
      </div>
    </template>
    
    <p>这是弹窗的主体内容。</p>
    <p>通过自定义指令,我们轻松实现了弹窗的拖拽交互。</p>
  </a-modal>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const isVisible = ref(false);
</script>
标签: Vue3

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。