基于Vue3实现高性能拖拽指令
作者:乐闻x
前言
在现代前端开发中,拖拽功能是增强用户体验的重要手段之一。本文将详细介绍如何在 Vue 3 中封装一个拖拽指令(v-draggable),并通过实战例子演示其实现过程。通过这篇教程,您将不仅掌握基础的拖拽功能,还能了解如何优化指令以提升其性能和灵活性,从而为您的项目增色。
封装拖拽指令思路
我们将封装一个简单的拖拽指令,名为 v-draggable,它允许我们在任何元素上添加拖拽功能。
指令逻辑
1.监听鼠标事件:我们需要监听 mousedown、mousemove 和 mouseup 事件。
2.计算拖动位置:根据鼠标移动的距离更新元素的位置。
3.清理事件:在拖动结束后移除事件监听器。
实现步骤
第一步:创建指令文件
在 src 目录下创建一个名为 directives 的文件夹,并在其中创建一个 draggable.js 文件:
// src/directives/draggable.js export default { mounted(el) { el.style.position = 'absolute'; let startX, startY, initialMouseX, initialMouseY; const mousemove = (e) => { const dx = e.clientX - initialMouseX; const dy = e.clientY - initialMouseY; el.style.top = `${startY + dy}px`; el.style.left = `${startX + dx}px`; }; const mouseup = () => { document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', mouseup); }; el.addEventListener('mousedown', (e) => { startX = el.offsetLeft; startY = el.offsetTop; initialMouseX = e.clientX; initialMouseY = e.clientY; document.addEventListener('mousemove', mousemove); document.addEventListener('mouseup', mouseup); e.preventDefault(); }); } };
第二步:注册指令
在 src 目录下的 main.js 文件中注册这个指令:
import { createApp } from 'vue'; import App from './App.vue'; import draggable from './directives/draggable'; const app = createApp(App); app.directive('draggable', draggable); app.mount('#app');
第三步:使用指令
现在我们可以在任何组件中使用这个拖拽指令。编辑 src/App.vue 文件:
<template> <div> <h1>Vue 3 拖拽指令示例</h1> <div v-draggable class="draggable-box">拖拽我!</div> </div> </template> <script> export default { name: 'App' }; </script> <style> .draggable-box { width: 150px; height: 150px; background-color: lightblue; text-align: center; line-height: 150px; cursor: move; user-select: none; } </style>
优化拖拽指令
当前的拖拽指令已经可以基本实现拖拽功能了,但还有一些细节需要优化,例如:
1.限制拖拽范围
2.支持触摸设备
3.添加节流来优化性能
4.提供一些配置选项
限制拖拽范围
我们可以通过对元素的位置进行限制,来防止其被拖出指定的范围。这里我们假定限制在父元素内进行拖拽。
// src/directives/draggable.js export default { mounted(el) { el.style.position = 'absolute'; let startX, startY, initialMouseX, initialMouseY; const mousemove = (e) => { const dx = e.clientX - initialMouseX; const dy = e.clientY - initialMouseY; let newTop = startY + dy; let newLeft = startX + dx; // 限制拖拽范围在父元素内 const parentRect = el.parentElement.getBoundingClientRect(); const elRect = el.getBoundingClientRect(); if (newLeft < 0) { newLeft = 0; } else if (newLeft + elRect.width > parentRect.width) { newLeft = parentRect.width - elRect.width; } if (newTop < 0) { newTop = 0; } else if (newTop + elRect.height > parentRect.height) { newTop = parentRect.height - elRect.height; } el.style.top = `${newTop}px`; el.style.left = `${newLeft}px`; }; const mouseup = () => { document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', mouseup); }; el.addEventListener('mousedown', (e) => { startX = el.offsetLeft; startY = el.offsetTop; initialMouseX = e.clientX; initialMouseY = e.clientY; document.addEventListener('mousemove', mousemove); document.addEventListener('mouseup', mouseup); e.preventDefault(); }); } };
支持触摸设备
为了支持触摸设备,我们需要添加 touchstart、touchmove 和 touchend 事件监听器。
// src/directives/draggable.js export default { mounted(el) { el.style.position = 'absolute'; let startX, startY, initialMouseX, initialMouseY; const move = (e) => { let clientX, clientY; if (e.touches) { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } else { clientX = e.clientX; clientY = e.clientY; } const dx = clientX - initialMouseX; const dy = clientY - initialMouseY; let newTop = startY + dy; let newLeft = startX + dx; const parentRect = el.parentElement.getBoundingClientRect(); const elRect = el.getBoundingClientRect(); if (newLeft < 0) { newLeft = 0; } else if (newLeft + elRect.width > parentRect.width) { newLeft = parentRect.width - elRect.width; } if (newTop < 0) { newTop = 0; } else if (newTop + elRect.height > parentRect.height) { newTop = parentRect.height - elRect.height; } el.style.top = `${newTop}px`; el.style.left = `${newLeft}px`; }; const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); document.removeEventListener('touchmove', move); document.removeEventListener('touchend', up); }; const down = (e) => { startX = el.offsetLeft; startY = el.offsetTop; if (e.touches) { initialMouseX = e.touches[0].clientX; initialMouseY = e.touches[0].clientY; } else { initialMouseX = e.clientX; initialMouseY = e.clientY; } document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); document.addEventListener('touchmove', move); document.addEventListener('touchend', up); e.preventDefault(); }; el.addEventListener('mousedown', down); el.addEventListener('touchstart', down); } };
添加节流优化性能
为了防止 mousemove 和 touchmove 事件触发得太频繁,我们可以使用节流(throttle)技术来优化性能。
// src/directives/draggable.js function throttle(func, limit) { let lastFunc; let lastRan; return function (...args) { const context = this; if (!lastRan) { func.apply(context, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(function () { if (Date.now() - lastRan >= limit) { func.apply(context, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } export default { mounted(el) { el.style.position = 'absolute'; let startX, startY, initialMouseX, initialMouseY; const move = throttle((e) => { let clientX, clientY; if (e.touches) { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } else { clientX = e.clientX; clientY = e.clientY; } const dx = clientX - initialMouseX; const dy = clientY - initialMouseY; let newTop = startY + dy; let newLeft = startX + dx; const parentRect = el.parentElement.getBoundingClientRect(); const elRect = el.getBoundingClientRect(); if (newLeft < 0) { newLeft = 0; } else if (newLeft + elRect.width > parentRect.width) { newLeft = parentRect.width - elRect.width; } if (newTop < 0) { newTop = 0; } else if (newTop + elRect.height > parentRect.height) { newTop = parentRect.height - elRect.height; } el.style.top = `${newTop}px`; el.style.left = `${newLeft}px`; }, 20); const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); document.removeEventListener('touchmove', move); document.removeEventListener('touchend', up); }; const down = (e) => { startX = el.offsetLeft; startY = el.offsetTop; if (e.touches) { initialMouseX = e.touches[0].clientX; initialMouseY = e.touches[0].clientY; } else { initialMouseX = e.clientX; initialMouseY = e.clientY; } document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); document.addEventListener('touchmove', move); document.addEventListener('touchend', up); e.preventDefault(); }; el.addEventListener('mousedown', down); el.addEventListener('touchstart', down); } };
提供配置选项
最后,我们可以通过指令的参数来提供一些配置选项,例如是否限制在父元素内拖拽。
const dx = clientX - initialMouseX; const dy = clientY - initialMouseY; let newTop = startY + dy; let newLeft = startX + dx; if (limitToParent) { const parentRect = el.parentElement.getBoundingClientRect(); const elRect = el.getBoundingClientRect(); if (newLeft < 0) { newLeft = 0; } else if (newLeft + elRect.width > parentRect.width) { newLeft = parentRect.width - elRect.width; } if (newTop < 0) { newTop = 0; } else if (newTop + elRect.height > parentRect.height) { newTop = parentRect.height - elRect.height; } } el.style.top = `${newTop}px`; el.style.left = `${newLeft}px`; }, 20); const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); document.removeEventListener('touchmove', move); document.removeEventListener('touchend', up); }; const down = (e) => { startX = el.offsetLeft; startY = el.offsetTop; if (e.touches) { initialMouseX = e.touches[0].clientX; initialMouseY = e.touches[0].clientY; } else { initialMouseX = e.clientX; initialMouseY = e.clientY; } document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); document.addEventListener('touchmove', move); document.addEventListener('touchend', up); e.preventDefault(); }; el.addEventListener('mousedown', down); el.addEventListener('touchstart', down); } };
使用配置选项
现在我们可以通过在使用指令时传递参数来控制是否限制拖拽范围。例如,编辑 src/App.vue:
<template> <div> <h1>Vue 3 拖拽指令示例</h1> <div v-draggable:limit class="draggable-box">拖拽我!</div> <div v-draggable class="draggable-box" style="margin-top: 200px;">我可以拖出容器</div> </div> </template> <script> export default { name: 'App' }; </script> <style> .draggable-box { width: 150px; height: 150px; background-color: lightblue; text-align: center; line-height: 150px; cursor: move; user-select: none; margin-bottom: 20px; } </style>
在上面的例子中,第一个 div 使用了 v-draggable:limit 指令,这意味着它的拖拽范围将被限制在父元素内。而第二个 div 则没有这个限制,可以自由拖动。
总结
通过本文的详细讲解,我们成功实现并优化了一个功能强大的拖拽指令 v-draggable。该指令不仅支持鼠标操作,还兼容触摸设备,并且通过节流机制有效地提升了性能。此外,我们还实现了限制拖拽范围的功能,使得该指令能够适应更多复杂的应用场景。希望本文能帮助您理解和掌握 Vue 3 中自定义指令的封装与优化技巧。
到此这篇关于基于Vue3实现高性能拖拽指令的文章就介绍到这了,更多相关Vue3拖拽指令内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!