vue拖拽自定义指令
在实现拖拽功能之前,首先需要理解鼠标事件的这几个属性clientY、pageY、screenY、layerY、offsetY怎么用,以及彼此之间有什么区别
- clientY 指的是鼠标相对于浏览器视口左上角的偏移(即距离可视页面左上角的距离,clientY在页面无滚动条的情况下值等同于pageY)
- pageY 指的是鼠标相对于页面左上角的偏移 (其值不会受滚动条的响)IE9之下并不支持这个属性
// pageY:鼠标相对于浏览器视口的偏移加上文档的滚动条隐藏的高度减去文档的clientTop.
const pageY = event.clientY +document.documentElement. scrollTop-document.documentElement.clientTop
为何要减去document.documentElement.clientTop,这是IE8之下浏览器特有的文档的偏移,即使设置html,body的padding和margin为0也不会影响其值。
- screenY 指的是鼠标相对于显示器屏幕左上角的偏移
- layerY 指的是找到它或它父级元素中最近具有定位的左上角距离
如果元素的position样式不是默认的static,我们说这个元素具有定位属性。
在当前触发鼠标事件的元素和它的祖先元素中找到最近的具有定位属性的元素,计算鼠标与其的偏移值,以找到元素的border的左上角的外交点作为相对点。如果找不到具有定位属性的元素,那么就相对于当前页面计算偏移,此时等同于pageY)
简单点说:layerY返回的是相对position:relative,position:absolute的left和top
- offsetY 指的是相对于它自己左上角的距离( IE专有的属性)
offsetY和layerY的不同在于,前者在计算偏移值时,相对于元素的border左上角的内交点,因此当鼠标位于元素的border上时,偏移值是一个负值。 另外offsetY并不在乎触发事件的元素是否有定位属性,它总是相对于触发事件的元素来计算偏移值
offsetX offsetY返回的是相对于当前元素的左上角的left和top
可以通过下面这张图了解下:
我们通过下面这张表看下他们之间的区别
相同点 | 不同点 | |
---|---|---|
clientY | 距离页面左上角距离 | 受页面滚动的影响 |
pageY | 距离页面左上角距离 | 不受页面滚动影响 |
相同点 | 不同点 | |
---|---|---|
layerY | 距离元素的左上角距离 | 受元素的定位的影响,会从本元素往上找到第一个定位的元素的左上角 |
offsetY | 距离元素左上角的距离 | 计算相对于本元素的左上角,不在乎定位问题,计算的是内交点。是IE浏览器的特有属性 |
拖拽原理及思路
主体思路:
拖拽主要用到了鼠标的几个事件
- onmousedown:鼠标按下事件
- onmousemove:鼠标移动事件
- onmouseup:鼠标抬起事件
基本原理:
根据鼠标的移动来移动被拖拽的元素,鼠标的移动即x、y坐标的变化;元素的移动即style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下。
拖拽状态 = 鼠标在元素上按下的时候{
记录鼠标x、y坐标
记录元素x、y坐标
}
鼠标在元素上移动的时候{
元素y = 现在鼠标y - 原来鼠标y + 原来元素y
元素x = 现在鼠标x - 原来鼠标x + 原来元素x
}
鼠标在任何时候放开的时候{
鼠标弹起来的时候不再移动
}
拖拽功能代码实现
// 在vue脚手架项目main.js中:
// 全局自定义拖拽组件
Vue.directive('drag', {
//1.指令绑定到元素上回立刻执行bind函数,只执行一次
//2.每个函数中第一个参数永远是el,表示绑定指令的元素,el参数是原生js对象
bind: function (el, elementObj) {
let dragBox = el; //获取当前元素
dragBox.style.position = 'absolute'; // 拖拽元素使用定位,脱离文档流
dragBox.onmousedown = e => {
//鼠标相对元素的位置
let disX = e.clientX - dragBox.offsetLeft;
let disY = e.clientY - dragBox.offsetTop;
document.onmousemove = e => {
//鼠标的位置减去鼠标相对元素的位置,得到元素的位置
let left = e.clientX - disX;
let top = e.clientY - disY;
//移动当前元素
dragBox.style.left = left + 'px';
dragBox.style.top = top + 'px';
};
document.onmouseup = e => {
//鼠标弹起来的时候不再移动
document.onmousemove = null;
//预防鼠标弹起来后还会循环(即预防鼠标放上去的时候还会移动)
document.onmouseup = null;
// 对外暴露元素相对于父级位置
elementObj.value.left = dragBox.style.left;
elementObj.value.top = dragBox.style.top;
};
};
}
});
// 使用
<div class="dragBox">
<div v-drag="elementPos" class="dragMe"></div>
</div>
<script>
export default {
name: 'dragRotateZoom',
data(){
return{
// 元素相对于父级位置
elementPos: {
left: null,
top: null,
},
}
},
watch:{
elementPos:{
handler(val){
console.log(val, 789);// 实时获取拖拽元素相对于父级位置
},
deep: true, immediate: true
}
}
</script>
// css
.dragBox{
width:300px;
height:300px;
background: #ccc;
position: relative;
.dragMe{
cursor: move;
width: 100px;
height: 100px;
background: burlywood;
display: flex;
justify-content: center;
align-items: center;
}
}
最终效果
大概就是这么多,使用的话直接通过v-drag即可实现拖拽功能,方便高效。