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

如何在 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>
http://www.xdnf.cn/news/10813.html

相关文章:

  • Spring @Autowired自动装配的实现机制
  • 手机邮箱APP操作
  • 风机巡检方案艰难之路
  • 全球高尔夫球场运营服务市场:现状、挑战与未来发展
  • Cursor使用最佳实践总结
  • 用 PyQt5 打造一个可视化 JSON 数据解析工具
  • 第二篇: 深入解析模型上下文协议(MCP):技术架构、核心组件与深远影响
  • SpringBoot2.3.1集成Knife4j接口文档
  • GirtHub访问不了解决方案
  • 【亲测有效】Mybatis-Plus中更新字段为null
  • android FragmentManager 删除所有Fragment 重建
  • EscapeX:去中心化游戏,开启极限娱乐新体验
  • Mybatis--创建mapper接口
  • IP话机和APP拨打电话的区别
  • Go语言学习-->从零开始搭建环境
  • FreeRTOS的简单介绍
  • 从0开始使用 Vue3 和 TypeScript 搭建项目详细教程
  • 1.Nodejs开发注意和前端区别
  • C语言:数据在内存中的存储
  • 关于list集合排序的常见方法
  • 《TCP/IP 详解 卷1:协议》第5章:Internet协议
  • 红黑树与红黑树的插入——用C++实现
  • Python----循环神经网络(BiLSTM:双向长短时记忆网络)
  • Rust 学习笔记:使用 cargo install 安装二进制 crate
  • 【氮化镓】GaN HMETs器件物理失效分析进展
  • NVIDIA CUDA Toolkit安装
  • 遥感影像建筑物变化检测
  • 前端与后端
  • [Java 基础]Java 是什么
  • 帝可得 - 设备管理