拖拽原理: 滑鼠按下時記錄初始坐标以及元素的初始位置,在滑鼠移動時計算目前坐标與初始坐标的內插補點,這個內插補點就是元素所移動的距離,注意最後移動的時候需要加上元素的初始位置。
觀察指令的鈎子,我們隻需要用到mounted。
const myDirective = {
// 在綁定元素的 attribute 前
// 或事件監聽器應用前調用
created(el, binding, vnode, prevVnode) {
// 下面會介紹各個參數的細節
},
// 在元素被插入到 DOM 前調用
beforeMount(el, binding, vnode, prevVnode) {},
// 在綁定元素的父元件
// 及他自己的所有子節點都挂載完成後調用
mounted(el, binding, vnode, prevVnode) {},
// 綁定元素的父元件更新前調用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在綁定元素的父元件
// 及他自己的所有子節點都更新後調用
updated(el, binding, vnode, prevVnode) {},
// 綁定元素的父元件解除安裝前調用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 綁定元素的父元件解除安裝後調用
unmounted(el, binding, vnode, prevVnode) {}
}
一個簡單的拖拽指令
- clientX 和 clientY 是滑鼠事件對象中的屬性,用于表示滑鼠相對于浏覽器視窗(視口)的水準和垂直坐标位置。
- offsetLeft 和 offsetTop 是 DOM 元素的屬性,用于表示該元素相對于其包含元素(父元素)的水準和垂直偏移量。換句話說,它們表示元素的位置,以像素為機關,相對于其父元素的左上角。在拖拽操作中,它們被用來記錄元素的初始位置,以便在拖拽過程中計算新的位置。
定義幾個變量,當調用 handleMouseDown 函數(滑鼠按下元素時)時,将初始的滑鼠坐标(event.clientX 和 event.clientY)以及元素的初始位置(el.offsetLeft 和 el.offsetTop)儲存在 initialX、initialY、offsetX 和 offsetY 中。同時開啟對滑鼠移動和松開事件的監聽。
import type { ObjectDirective } from 'vue';
const dragSimple: ObjectDirective<HTMLElement> = {
mounted(el) {
el.style.position = 'absolute';
el.style.zIndex = '999';
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
const handleMouseDown = (event: MouseEvent) => {
if (event.button !== 0)
return; // 檢查是否是滑鼠左鍵
initialX = event.clientX;
initialY = event.clientY;
offsetX = el.offsetLeft;
offsetY = el.offsetTop;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (event: MouseEvent) => {
const dx = event.clientX - initialX;
const dy = event.clientY - initialY;
el.style.left = `${offsetX + dx}px`;
el.style.top = `${offsetY + dy}px`;
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
el.addEventListener('mousedown', handleMouseDown);
},
};
export { dragSimple };
- 使用
<div v-drag-simple class="fixed-button">
<a-button type="primary" size="large" shape="circle" @click="visible = true">
<template #icon>
<Icon icon="setting-outlined " />
</template>
</a-button>
</div>
試了一下,拖拽非常的絲滑,沒有問題,但是我們發現在拖拽結束的時候click事件被觸發了,我和我們想要的效果不符,我們希望在拖拽的時候不要觸發這個點選事件,隻有在點選的時候才觸發。下面提供兩個解決方案。
使用門檻值
設定一個門檻值CLICK_THRESHOLD,當滑鼠移動距離不超過門檻值就認為是點選事件,超過則為拖拽事件。這種寫法需要外部傳遞一個函數。
import type { ObjectDirective } from 'vue';
const CLICK_THRESHOLD = 5;
const dragSimple: ObjectDirective<HTMLElement> = {
mounted(el, { value }) {
el.style.position = 'absolute';
el.style.zIndex = '999';
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
let isDragging = false; // 添加一個标記,表示目前是否正在拖拽
const handleMouseMove = (event: MouseEvent) => {
const dx = event.clientX - initialX;
const dy = event.clientY - initialY;
el.style.left = `${offsetX + dx}px`;
el.style.top = `${offsetY + dy}px`;
// 判斷是否開始拖拽
if (!isDragging) {
const distance = Math.sqrt(dx * dx + dy * dy); // 計算直角三角形的斜邊長度
if (distance >= CLICK_THRESHOLD)
isDragging = true;
// 在門檻值外觸發拖拽事件
// 這裡可以觸發拖拽事件的回調函數
}
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
el.removeEventListener('mouseup', handleMouseUp); // 移除mouseup事件監聽器
if (!isDragging) {
// 在門檻值内觸發點選事件
// 這裡可以觸發點選事件的回調函數
value();
}
isDragging = false; // 重置拖拽标記
};
const handleMouseDown = (event: MouseEvent) => {
if (event.button !== 0)
return; // 檢查是否是滑鼠左鍵
initialX = event.clientX;
initialY = event.clientY;
offsetX = el.offsetLeft;
offsetY = el.offsetTop;
document.addEventListener('mousemove', handleMouseMove);
el.addEventListener('mouseup', handleMouseUp); // 将mouseup事件監聽器添加到元素el上
};
el.addEventListener('mousedown', handleMouseDown);
},
};
export { dragSimple };
- 使用
<div v-drag-simple="() => visible = true" class="fixed-button">
<a-button type="primary" size="large" shape="circle">
<template #icon>
<Icon icon="setting-outlined " />
</template>
</a-button>
</div>
直接使用pointer-events
有一種更簡便的方法,就是在滑鼠移動的時候取消元素的 pointer-events,在滑鼠松開的時候再恢複。
import type { ObjectDirective } from 'vue';
const dragSimple: ObjectDirective<HTMLElement> = {
mounted(el) {
el.style.position = 'absolute';
el.style.zIndex = '999';
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
const handleMouseDown = (event: MouseEvent) => {
if (event.button !== 0)
return; // 檢查是否是滑鼠左鍵
initialX = event.clientX;
initialY = event.clientY;
offsetX = el.offsetLeft;
offsetY = el.offsetTop;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (event: MouseEvent) => {
const dx = event.clientX - initialX;
const dy = event.clientY - initialY;
el.style.pointerEvents = 'none'; // 取消元素的 pointer-events,防止拖拽過程中觸發子元素的事件
el.style.left = `${offsetX + dx}px`;
el.style.top = `${offsetY + dy}px`;
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
el.style.pointerEvents = ''; // 恢複元素的 pointer-events
};
el.addEventListener('mousedown', handleMouseDown);
},
};
export { dragSimple };
加個小功能,重新整理後還能保持位置
/* eslint-disable @typescript-eslint/no-use-before-define */
import type { ObjectDirective } from 'vue';
const dragSimple: ObjectDirective<HTMLElement> = {
mounted(el) {
el.style.position = 'absolute';
el.style.zIndex = '999';
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
const POSITION_STORAGE_KEY = 'dragPosition';
const handleMouseDown = (event: MouseEvent) => {
if (event.button !== 0)
return; // 檢查是否是滑鼠左鍵
initialX = event.clientX;
initialY = event.clientY;
offsetX = el.offsetLeft;
offsetY = el.offsetTop;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (event: MouseEvent) => {
const dx = event.clientX - initialX;
const dy = event.clientY - initialY;
el.style.pointerEvents = 'none'; // 取消元素的 pointer-events,防止拖拽過程中觸發子元素的事件
el.style.left = `${offsetX + dx}px`;
el.style.top = `${offsetY + dy}px`;
// 儲存位置資訊到 localStorage
const positionInfo = {
left: el.style.left,
top: el.style.top,
};
localStorage.setItem(POSITION_STORAGE_KEY, JSON.stringify(positionInfo));
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
el.style.pointerEvents = ''; // 恢複元素的 pointer-events
};
el.addEventListener('mousedown', handleMouseDown);
// 頁面加載時,從 localStorage 中恢複位置資訊
const storedPositionInfo = localStorage.getItem(POSITION_STORAGE_KEY);
if (storedPositionInfo) {
const { left, top } = JSON.parse(storedPositionInfo);
el.style.left = left ?? 0;
el.style.top = top ?? 0;
}
},
};
export { dragSimple };
文章結束啦~