当前位置: 首页 > news >正文

如何在 vue 中实现一个自定义拖拽的指令或插件

前言

在 Vue 中,我们可以通过 自定义指令插件 给任意元素添加拖拽的功能。使用 Vue.directive() 注册全局指令或者使用 Vue.use() 安装插件,这两种方式都可以实现实现动态拖拽的功能。

区别
特性Vue.directive()Vue.use()
功能范围直接注册单个全局指令安装一个插件(可包含多个指令、混入等)
复杂度简单快捷,适合单一功能适合复杂功能或需要配置的场景
可配置性无法传递参数支持传递配置对象
标准化程度高(符合 Vue 插件规范)
实现

下面我们以自定义指令举例,探讨如何实现这个功能

  • 步骤 1:创建自定义指令
// draggable.js
const draggable = {inserted(el, binding) {initConfig(el, binding);checkVisibilityAndBind(el);},update(el, binding) {initConfig(el, binding);  // 更新配置checkVisibilityAndBind(el);},unbind(el) {cleanup(el);}
};// 初始化/更新配置
function initConfig(el, binding) {let isEnabled = true;let config = {};// 解析指令值类型(应对各种可能的传值场景)if (typeof binding.value === 'boolean') {// 布尔值直接控制启用状态isEnabled = binding.value;} else if (typeof binding.value === 'object' && binding.value !== null) {// 对象配置:合并 isEnabled 和其他参数isEnabled = binding.value.isEnabled !== undefined ? binding.value.isEnabled : true;config = { ...binding.value };} else {// 其他类型(如字符串选择器)默认启用isEnabled = binding.value !== false;config = { handleSelector: binding.value };}// 合并最终配置el._dragConfig = {bounds: {},handleSelector: null,draggingClass: 'dragging',...config,isEnabled // 确保 isEnabled 优先级最高};
}function checkVisibilityAndBind(el) {const { isEnabled } = el._dragConfig;const isVisible = getComputedStyle(el).display !== 'none';if (isVisible && isEnabled && !el._dragHandlers) {bindDrag(el);} else if ((!isVisible || !isEnabled) && el._dragHandlers) {cleanup(el);}
}function bindDrag(el) {const {handleSelector,draggingClass,bounds} = el._dragConfig;let currentX = 0, currentY = 0, startX = 0, startY = 0;const dragHandle = handleSelector ? el.querySelector(handleSelector) : el;if (!dragHandle){return;}const onMouseDown = (e) => {// 检查是否点击在句柄区域const isHandle = handleSelector ? e.target.closest(handleSelector) === dragHandle : true;if (!isHandle){return;}e.preventDefault();// 添加拖拽样式类if (draggingClass){el.classList.add(draggingClass);}// 记录初始位置startX = e.clientX;startY = e.clientY;const style = window.getComputedStyle(el);const matrix = new DOMMatrix(style.transform);currentX = matrix.m41;currentY = matrix.m42;// 拖拽移动处理const onMouseMove = (event) => {const dx = event.clientX - startX;const dy = event.clientY - startY;// 计算新位置(应用边界限制)let newX = currentX + dx;let newY = currentY + dy;if (bounds.minX !== undefined) newX = Math.max(newX, bounds.minX);if (bounds.maxX !== undefined) newX = Math.min(newX, bounds.maxX);if (bounds.minY !== undefined) newY = Math.max(newY, bounds.minY);if (bounds.maxY !== undefined) newY = Math.min(newY, bounds.maxY);el.style.transform = `translate(${newX}px, ${newY}px)`;};// 拖拽结束处理const onMouseUp = () => {// 移除拖拽样式类if (draggingClass){el.classList.remove(draggingClass)}document.removeEventListener('mousemove', onMouseMove);document.removeEventListener('mouseup', onMouseUp);};document.addEventListener('mousemove', onMouseMove);document.addEventListener('mouseup', onMouseUp);};// 保存引用以便清理dragHandle.addEventListener('mousedown', onMouseDown);dragHandle.style.cursor = 'move';el._dragHandlers = { onMouseDown, dragHandle };
}function cleanup(el) {if (el._dragHandlers) {const { dragHandle, onMouseDown } = el._dragHandlers;// 移除事件监听dragHandle.removeEventListener('mousedown', onMouseDown);dragHandle.style.cursor = '';// 确保移除样式类if (el._dragConfig.draggingClass) {el.classList.remove(el._dragConfig.draggingClass);}delete el._dragHandlers;}
}export default draggable;
  • 步骤 2:注册指令
// main.js
import Vue from 'vue';
import draggable from './draggable';Vue.directive('draggable', draggable);
  • 步骤 3:使用指令
<template><div v-draggable="{handleSelector: '.handle',draggingClass: 'active-drag',bounds: { minX: 0, minY: 0 }}"style="position: absolute; background: #fff; border: 2px solid #ccc;"><!-- 拖拽句柄 --><div class="handle" style="padding: 8px; background: #eee;">[拖拽区]</div><!-- 内容区域 --><div style="padding: 12px;"><p>可自由选中的文字内容...</p><button @click="showPos">获取当前位置</button></div></div>
</template><script>
export default {methods: {showPos() {const el = this.$el.querySelector('[v-draggable]');const style = window.getComputedStyle(el);const matrix = new DOMMatrix(style.transform);alert(`当前位置: X=${matrix.m41}px, Y=${matrix.m42}px`);}}
};
</script><style>
.active-drag {box-shadow: 0 2px 8px rgba(0,0,0,0.15);opacity: 0.9;
}
</style>
关键点和注意事项
  1. 动态绑定
    使用 v-show 控制显示时,通过 checkVisibilityAndBind() 检测元素显示状态,动态绑定/解绑事件。
  2. 位置计算
    通过 transform: translate() 实现位移,避免影响布局。记录初始位移值实现累加拖拽。
  3. 事件管理
    鼠标按下时绑定移动/松开事件。
    元素隐藏或销毁时,自动移除所有相关事件。
更多的使用场景演示
  • 场景 1:直接禁用拖拽
<template><div v-draggable="false"> <!-- 完全禁用 -->不可拖拽的内容</div>
</template>
  • 场景 2:对象配置动态禁用
<template><div v-draggable="{ isEnabled: allowDrag, handleSelector: '.handle',bounds: { maxX: 500 }}"><div class="handle">拖拽手柄</div><div>内容区域(拖拽状态:{{ allowDrag ? '启用' : '禁用' }})</div></div>
</template><script>
export default {data() {return {allowDrag: true};}
};
</script>
  • 场景 3:通过选择器隐式启用
<template><!-- 传递选择器字符串,默认启用拖拽 --><div v-draggable="'.custom-handle'"><div class="custom-handle">自定义拖拽区</div><div>可拖拽内容</div></div>
</template>

相关文章:

  • qt 事件顺序
  • Laravel模型状态:深入理解Eloquent的隐秘力量
  • QT常用控件(1)
  • metersphere不同域名的参数在链路测试中如何传递?
  • 项目任务,修改svip用户的存储空间。
  • 微博app 最新版本15.5.2 mfp 分析
  • RagFlow优化代码解析(一)
  • 操作系统:生态思政
  • 现代密码学 | 椭圆曲线密码学—附py代码
  • 如何从系统日志中排查磁盘错误?
  • 0518蚂蚁暑期实习上机考试题1:数组操作
  • “轻量应用服务器” vs. “云服务器CVM”:小白入门腾讯云,哪款“云机”更适合你?(场景、配置、价格对比解析)
  • 神经符号集成-三篇综述
  • Docker 镜像(或 Docker 容器)中查找文件命令
  • 2023-2025 时序大模型相关工作汇总
  • 生产环境中安装和配置 Nginx 以部署 Flask 应用的详细指南
  • 架构设计的目标:高内聚、低耦合的本质
  • Cat.1与Cat.4区别及应用场景
  • 【知识点】第4章:程序控制结构
  • 信息过载时,如何筛选重要信息
  • dw做网站视频教程/各行业关键词
  • 如何编辑网站后台/atp最新排名
  • h5营销型网站功能/百度seo竞价推广是什么
  • 我国中小企业网站建设/seo是什么岗位简称
  • 门窗网站源码/接app推广
  • 手机制作最简单钓鱼网站/常见的网络营销平台有哪些