在前端开发中,我们经常遇到这样的需求:用户需要拖动一个弹窗、卡片、对话框等元素。虽然市面上有很多现成的库,但你有没有想过,其实我们只需要十几行代码,就能用 Vue 3 的 自定义指令 实现这一效果?
本文将带你从零开始,一步步实现一个支持 v-drag 拖拽指令,轻松控制 DOM 元素移动,彻底掌握 Vue 3 自定义指令的高级用法。
最终效果
你可以为任意元素加上 `v-drag` 指令,即可实现拖动效果。还能通过 `.v-drag-handle` 类名指定“只拖动标题栏”的行为。
<div class=”my-dialog” v-drag zindexEnable fixed>
<div class=”v-drag-handle”>拖动我试试~</div>
<p>内容内容内容…</p>
</div>
为什么用指令而不是组件?
虽然组件封装更彻底,但拖拽是一种“DOM 行为增强”,使用自定义指令更轻量、解耦性更高,几乎可以应用到任意元素,无需额外结构。这也是指令非常适合做拖拽的原因。
第一步:创建自定义指令 v-drag
我们先新建一个 v-drag.js 文件,实现一个 Vue 插件,提供 v-drag 指令。
// v-drag.js
let baseZindex = 999;
let index = 0;
let max = 0;
const createDirective = (name = ‘drag’) => ({
install(app) {
app.directive(name, {
mounted(el) {
const isFixed = el.hasAttribute(“fixed”);
const handle = el.querySelector(‘.v-drag-handle’) || el;
const zindexEnable = el.hasAttribute(“zindexEnable”);
let disX = 0;
let disY = 0;
let curClientX = 0;
let curClientY = 0;
if (zindexEnable) {
el.dataset.zindex = index;
el.style.zIndex = baseZindex + index;
}
index++;
max = index;
const onMouseDown = (e) => {
curClientX = e.clientX;
curClientY = e.clientY;
const rect = el.getBoundingClientRect();
disX = e.clientX – (isFixed ? rect.left : el.offsetLeft);
disY = e.clientY – (isFixed ? rect.top : el.offsetTop);
if (zindexEnable && Number(el.dataset.zindex) < max) {
el.dataset.zindex = ++max – 1;
el.style.zIndex = baseZindex + max – 1;
}
document.addEventListener(‘mousemove’, onMouseMove);
document.addEventListener(‘mouseup’, onMouseUp);
};
const onMouseMove = (e) => {
const left = e.clientX – disX;
const top = e.clientY – disY;
el.style.position = isFixed ? ‘fixed’ : ‘absolute’;
Object.assign(el.style, {
left: `${left}px`,
top: `${top}px`,
right: ‘auto’,
bottom: ‘auto’,
});
};
const onMouseUp = () => {
document.removeEventListener(‘mousemove’, onMouseMove);
document.removeEventListener(‘mouseup’, onMouseUp);
};
handle.addEventListener(‘mousedown’, onMouseDown);
el._dragCleanup = () => {
handle.removeEventListener(‘mousedown’, onMouseDown);
};
},
unmounted(el) {
el._dragCleanup?.();
delete el._dragCleanup;
}
});
}
});
export default createDirective();
第二步:注册全局指令
在 main.js 中全局注册该插件:
// main.js
import { createApp } from ‘vue’;
import App from ‘./App.vue’;
import vDrag from ‘./v-drag.js’;
const app = createApp(App);
app.use(vDrag); // 默认指令名是 v-drag
app.mount(‘#app’);
第三步:使用拖拽指令
你现在可以在任何组件中使用 v-drag,也可以通过加 zindexEnable 和 fixed 控制叠层与定位模式。
<template>
<div class=”dialog” v-drag fixed zindexEnable>
<div class=”v-drag-handle”>标题拖动区</div>
<p>这是一个可拖拽弹窗!</p>
</div>
</template>
关于我
最近在学习油猴脚本开发,写了很多有趣的脚本:
接口拦截工具:修改CSDN博客数据接口返回值
Vue路由一键切换:开发效率起飞
任意元素双击实现画中画:摸鱼超级助手
掘金后台自动签到助手
解除文本复制、网页复制、一键下载为MD
主题切换助手