基于Vue3實(shí)現(xiàn)高性能拖拽指令
前言
在現(xiàn)代前端開發(fā)中,拖拽功能是增強(qiáng)用戶體驗(yàn)的重要手段之一。本文將詳細(xì)介紹如何在 Vue 3 中封裝一個(gè)拖拽指令(v-draggable),并通過實(shí)戰(zhàn)例子演示其實(shí)現(xiàn)過程。通過這篇教程,您將不僅掌握基礎(chǔ)的拖拽功能,還能了解如何優(yōu)化指令以提升其性能和靈活性,從而為您的項(xiàng)目增色。
封裝拖拽指令思路
我們將封裝一個(gè)簡(jiǎn)單的拖拽指令,名為 v-draggable,它允許我們?cè)谌魏卧厣咸砑油献Чδ堋?/p>
指令邏輯
1.監(jiān)聽鼠標(biāo)事件:我們需要監(jiān)聽 mousedown、mousemove 和 mouseup 事件。
2.計(jì)算拖動(dòng)位置:根據(jù)鼠標(biāo)移動(dòng)的距離更新元素的位置。
3.清理事件:在拖動(dòng)結(jié)束后移除事件監(jiān)聽器。
實(shí)現(xiàn)步驟
第一步:創(chuàng)建指令文件
在 src 目錄下創(chuàng)建一個(gè)名為 directives 的文件夾,并在其中創(chuàng)建一個(gè) 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();
});
}
};
第二步:注冊(cè)指令
在 src 目錄下的 main.js 文件中注冊(cè)這個(gè)指令:
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');
第三步:使用指令
現(xiàn)在我們可以在任何組件中使用這個(gè)拖拽指令。編輯 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>
優(yōu)化拖拽指令
當(dāng)前的拖拽指令已經(jīng)可以基本實(shí)現(xiàn)拖拽功能了,但還有一些細(xì)節(jié)需要優(yōu)化,例如:
1.限制拖拽范圍
2.支持觸摸設(shè)備
3.添加節(jié)流來優(yōu)化性能
4.提供一些配置選項(xiàng)
限制拖拽范圍
我們可以通過對(duì)元素的位置進(jìn)行限制,來防止其被拖出指定的范圍。這里我們假定限制在父元素內(nèi)進(jìn)行拖拽。
// 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;
// 限制拖拽范圍在父元素內(nèi)
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();
});
}
};
支持觸摸設(shè)備
為了支持觸摸設(shè)備,我們需要添加 touchstart、touchmove 和 touchend 事件監(jiān)聽器。
// 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);
}
};
添加節(jié)流優(yōu)化性能
為了防止 mousemove 和 touchmove 事件觸發(fā)得太頻繁,我們可以使用節(jié)流(throttle)技術(shù)來優(yōu)化性能。
// 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);
}
};
提供配置選項(xiàng)
最后,我們可以通過指令的參數(shù)來提供一些配置選項(xiàng),例如是否限制在父元素內(nèi)拖拽。
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);
}
};
使用配置選項(xiàng)
現(xiàn)在我們可以通過在使用指令時(shí)傳遞參數(shù)來控制是否限制拖拽范圍。例如,編輯 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>
在上面的例子中,第一個(gè) div 使用了 v-draggable:limit 指令,這意味著它的拖拽范圍將被限制在父元素內(nèi)。而第二個(gè) div 則沒有這個(gè)限制,可以自由拖動(dòng)。
總結(jié)
通過本文的詳細(xì)講解,我們成功實(shí)現(xiàn)并優(yōu)化了一個(gè)功能強(qiáng)大的拖拽指令 v-draggable。該指令不僅支持鼠標(biāo)操作,還兼容觸摸設(shè)備,并且通過節(jié)流機(jī)制有效地提升了性能。此外,我們還實(shí)現(xiàn)了限制拖拽范圍的功能,使得該指令能夠適應(yīng)更多復(fù)雜的應(yīng)用場(chǎng)景。希望本文能幫助您理解和掌握 Vue 3 中自定義指令的封裝與優(yōu)化技巧。
到此這篇關(guān)于基于Vue3實(shí)現(xiàn)高性能拖拽指令的文章就介紹到這了,更多相關(guān)Vue3拖拽指令內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue組件實(shí)現(xiàn)移動(dòng)端九宮格轉(zhuǎn)盤抽獎(jiǎng)
這篇文章主要為大家詳細(xì)介紹了vue組件實(shí)現(xiàn)移動(dòng)端九宮格轉(zhuǎn)盤抽獎(jiǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
vue router解決路由帶參數(shù)跳轉(zhuǎn)時(shí)出現(xiàn)404問題
我的頁面是從一個(gè)vue頁面router跳轉(zhuǎn)到另一個(gè)vue頁面,并且利用windows.open() 瀏覽器重新創(chuàng)建一個(gè)頁簽,但是不知道為什么有時(shí)候可以有時(shí)候又不行,所以本文給大家介紹了vue router解決路由帶參數(shù)跳轉(zhuǎn)時(shí)出現(xiàn)404問題,需要的朋友可以參考下2024-03-03
vue+element UI實(shí)現(xiàn)樹形表格
這篇文章主要為大家詳細(xì)介紹了vue+element UI實(shí)現(xiàn)樹形表格,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12
Vue項(xiàng)目中new?Vue()和export?default{}的區(qū)別說明
這篇文章主要介紹了Vue項(xiàng)目中new?Vue()和export?default{}的區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
vuejs實(shí)現(xiàn)本地?cái)?shù)據(jù)的篩選分頁功能思路詳解
今天做項(xiàng)目需要一份根據(jù)本地?cái)?shù)據(jù)的篩選分頁功能,下面小編把vuejs實(shí)現(xiàn)本地?cái)?shù)據(jù)的篩選分頁功能的實(shí)現(xiàn)思路分享到腳本之家平臺(tái),需要的朋友可以參考下2017-11-11
使用vuex緩存數(shù)據(jù)并優(yōu)化自己的vuex-cache
這篇文章主要介紹了使用vuex緩存數(shù)據(jù)并優(yōu)化自己的vuex-cache,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
解決Vue中mounted鉤子函數(shù)獲取節(jié)點(diǎn)高度出錯(cuò)問題
本篇文章給大家分享了如何解決Vue中mounted鉤子函數(shù)獲取節(jié)點(diǎn)高度出錯(cuò)問題,對(duì)此有興趣的朋友可以參考學(xué)習(xí)下。2018-05-05

