手摸手实现vue拖拽组件-限制拖拽范围
代码&效果
先上效果
hooks
/*
* @version: 1.0
* @Author: fubobo
* @Date: 2025-03-03 10:38:47
* @LastEditors: fubobo
* @LastEditTime: 2025-03-04 15:18:44
* @Descripttion: box移动,限制在父元素移动
*/
class UseDrag {
parentDomRef = ref<HTMLElement>();
dragDomRef = ref<HTMLElement>();
// 移动元素
dragElement = ref({
isMouseDown: false, // 保存鼠标点击下去的状态
x: 240,
y: 0,
preX: 240, // 上一次box的X
preY: 0, // 上一次box的Y
maxX: 0, // 可移动最大X
maxY: 0, // 可移动最大Y
});
// 鼠标原始点
mousePoint = ref({
x: 0,
y: 0,
});
// 子元素样式
computeStyle = computed(() => {
return `transform: translate(${this.dragElement.value.x}px, ${this.dragElement.value.y}px)`;
});
constructor() {
onMounted(() => {
this.calculateBoundary();
this.dragDomRef.value?.addEventListener('mousedown', this.startDrag);
window.addEventListener('mousemove', this.drag);
window.addEventListener('mouseup', this.stopDrag);
});
onBeforeUnmount(() => {
window.removeEventListener('mousemove', this.drag);
window.removeEventListener('mouseup', this.stopDrag);
this.dragDomRef.value?.removeEventListener('mousedown', this.startDrag);
});
}
// 计算边界限制
protected calculateBoundary() {
const parentRect = this.parentDomRef.value?.getBoundingClientRect();
const sonRect = this.dragDomRef.value?.getBoundingClientRect();
this.dragElement.value.maxX =
(parentRect?.width as number) - (sonRect?.width as number);
this.dragElement.value.maxY =
(parentRect?.height as number) - (sonRect?.height as number);
}
// 鼠标点击后开始移动
protected startDrag = (e: MouseEvent) => {
this.dragElement.value.isMouseDown = true;
const parentRect = this.parentDomRef.value?.getBoundingClientRect();
// 储存原始点,以父box坐上角为坐标轴,计算鼠标初始坐标
this.mousePoint.value = {
x: e.clientX - (parentRect?.x as number),
y: e.clientY - (parentRect?.y as number),
};
};
// 拖动
protected drag = (e: MouseEvent) => {
const parentRect = this.parentDomRef.value?.getBoundingClientRect();
if (this.dragElement.value.isMouseDown) {
// 计算鼠标相对于拖拽box的偏移量
const offsetX = this.mousePoint.value.x - this.dragElement.value.preX;
const offsetY = this.mousePoint.value.y - this.dragElement.value.preY;
// 限制边界
const newX = Math.min(
e.clientX - (parentRect?.x as number) - offsetX,
this.dragElement.value.maxX
);
const newY = Math.min(
e.clientY - (parentRect?.y as number) - offsetY,
this.dragElement.value.maxY
);
this.dragElement.value.x = newX > 0 ? newX : 0;
this.dragElement.value.y = newY > 0 ? newY : 0;
}
};
// 停止拖动
protected stopDrag = () => {
// 更新上一次拖拽box的坐标
this.dragElement.value.preX = this.dragElement.value.x;
this.dragElement.value.preY = this.dragElement.value.y;
this.dragElement.value.isMouseDown = false;
};
}
export default UseDrag;
inde.vue
<div class="home-box" ref="parentDomRef">
<div class="drag-box" ref="dragDomRef" :style="computeStyle"></div>
</div>
</template>
<script lang="ts" setup>
import UseDrag from "./hooks/useDrag";
// 元素移动相关
const { parentDomRef, dragDomRef, computeStyle } = new UseDrag();
</script>
<style>
.home-box {
position: relative;
margin: 50px auto;
width: 360px;
height: 500px;
background: #ccc;
}
.drag-box {
position: absolute;
width: 120px;
height: 245px;
background: black;
cursor: move;
}
</style>
实现思路
- 明白鼠标拖动dom的基本实现
基本实现首先要清楚dom上的一些基本知识,这些属性可以通过DOM API getBoundingClientRect()来获取
{
bottom:430, //元素底部距离窗口顶部的距离 (等于 y + height)
height:340, //元素的高度
left:120, //元素左侧距离窗口左侧的距离
right:460, //元素右侧距离窗口左侧的距离(等于 x + width)
top:90, //元素顶部距离窗口的距离
width:340, //元素的宽度
x:120, //元素左上角相对于视口的横坐标
y:90 //元素左上角相对于视口的纵坐标
}
其次就是一些鼠标事件(这里只列举了组件用到的鼠标事件)
mousedown:鼠标按下左键时被触发
mousemove: 鼠标移动时出触发
mouseup: 鼠标左键松开触发
通过鼠标的这三个事件可以做到监听鼠标的拖动,需要借助一个变量(isMouseDown)来标识鼠标是否按下下,这样可以确保鼠标移动的时候只有在点击状态时触发拖拽事件。
一般拖拽实现是通过监听鼠标拖拽的变化值来去改变需要被拖拽box的left和right(也可以使用transform: translate(x,y)来实现),基本拖拽的实现原理鼠标、被拖拽box都是相对于浏览器左上角为坐标轴来进行定位。
- 转化鼠标以及拖拽box相对父元素的坐标
那有直接父元素的话,鼠标和被拖拽物体就可以基于父元素来进行定位,其实在没有直接父元素的情况下,html不就是父元素吗,所以我的实现思路就是借助来这一点。如何转化?
// 在鼠标刚点击时候,记录鼠标相对于父box的下x,y轴
const parentRect = this.parentDomRef.value?.getBoundingClientRect();
// 储存原始点,以父box坐上角为坐标轴,计算鼠标初始坐标
this.mousePoint.value = {
x: e.clientX - (parentRect?.x as number),
y: e.clientY - (parentRect?.y as number),
};
转化完成后,当鼠标移动的时候,记录其与初始位置的移动距离,计算公式如下
被拖拽box新位置.x = 当前鼠标相对于父元素的位置.x - (鼠标原始点.x - 被拖拽box拖拽开始前的位置.x)
y轴同理
最后在鼠标松开时候(mouseup)更新被拖拽物位置信息,为下一次拖拽保存被拖拽物拖拽开始前的位置,如果没有这步,那在下一次点击时候被拖拽box会回到最原始的位置
- 计算限制区域范围
最后计算范围还是很简单的,因为现在已经根据父元素来定位了,所以maxX = fatherWidth - sonWidth,Y也同理,极小值的话都是0。 有一个点需要注意,鼠标移动以及鼠标抬起需要挂载到windows下来监听,因为鼠标可能会移除被拖拽box
结尾
瞎编不易,~