JS获取鼠标相对元素的位置


最近项目上遇到个需求,即页面上存在一个方框,按下鼠标后开始拖动,松开鼠标后停止。在这里记录一下开发心得

思路

逻辑上不难,无非就是分如下两步

  • 触发目标元素的mousedown事件时,获取鼠标相对元素的位置x、y
  • 在页面的mousemove事件中,保证x、y永远不变

问题

思路理清了,主要解决点就是如何获取鼠标相对元素的位置了。

可以通过事件对象e.pageX/e.pageY来获取鼠标距离浏览器左/上的距离,然后只要获取拖拽元素距离浏览器左/上的距离,减去就可以得到所需位置了。

但这个距离怎么拿到呢?offsetLeft/offsetRight?

だめだよ、offset是相对于offsetParent的边界的位置,即最近的拥有定位的父元素或者table,td,th,body元素,而我需要获取相对于页面边界的距离,这显然是无法使用的。

getBoundingClientRect

详细参考,这里只做简单描述。

该方法通过元素调用element.getBoundingClientRect(),不需要传递参数。返回一个对象,包括了8个描述元素的属性,分别为:

  • width:元素宽度
  • height:元素高度
  • left:元素左边界距离视窗左边界的距离
  • top:元素上边界距离视窗上边界的距离
  • right:元素右边界距离视窗左边界的距离
  • bottom:元素下边界距离视窗上边界的距离
  • x和y:官方未给描述,从数值来看和top与left值相等,什么作用我暂且蒙在崛川雷鼓里

MDN给出的直观图

既然这样,只需要通过如下方式就可以获取鼠标在元素里的位置

1
2
3
4
// 距离元素左侧距离
e.pageX - element.getBoundingClientRect().left
// 距离元素上方距离
e.pageY - element.getBoundingClientRect().top

需求实现(vue)

template部分

1
2
3
<div ref="mainRef" class="main">
<div ref="areaRef" class="area" @mousedown.prevent="areaMouseDown($event)"></div>
</div>

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
export default {
data() {
return {
areaStartMove: false, // 目标是否开始移动
mainAttr: {}, // 目标的容器定位数据
areaAttr: {} // 目标定位数据
}
},
// 组件加载时注册鼠标移动和弹起事件
mounted() {
document.body.addEventListener('mousemove', this.areaMouseMove)
document.body.addEventListener('mouseup', this.areaMouseUp)
},
// 组件卸载时注册鼠标移动和弹起事件
destroyed() {
document.body.removeEventListener('mousemove', this.areaMouseMove)
document.body.removeEventListener('mouseup', this.areaMouseUp)
},
methods: {
areaMouseDown(e) {
const areaElement = e.currentTarget
this.areaStartMove = true
// 分别获取目标容器和目标的定位数据
this.mainAttr = this.$refs.mainRef.getBoundingClientRect()
this.areaAttr = areaElement.getBoundingClientRect()
// 获取鼠标距离目标左侧和上方的距离,存入目标定位数据中
this.areaAttr.clientX = e.pageX - this.areaAttr.left
this.areaAttr.clientY = e.pageY - this.areaAttr.top
},
areaMouseMove(e) {
// 如果目标为移动状态, 再执行相关方法
if (this.areaStartMove) {
const areaElement = this.$refs.areaRef
// 根据固定的鼠标距离目标左侧和上方的距离,分别求得目标此时绝对定位的left和top
const top = e.pageY - this.mainAttr.top - this.areaAttr.clientY
const left = e.pageX - this.mainAttr.left - this.areaAttr.clientX
// 获取最大left和top
const maxTop = this.mainAttr.height - this.areaAttr.height
const maxLeft = this.mainAttr.width - this.areaAttr.width
// 赋予样式值,移动时不能超出边界
if (top <= 0) {
areaElement.style.top = '0'
} else if (top >= maxTop) {
areaElement.style.top = maxTop + 'px'
} else {
areaElement.style.top = top + 'px'
}
if (left <= 0) {
areaElement.style.left = '0'
} else if (left >= maxLeft) {
areaElement.style.left = maxLeft + 'px'
} else {
areaElement.style.left = left + 'px'
}
}
},
// 鼠标弹起,移除目标的移动状态
areaMouseUp() {
if (this.areaStartMove) {
this.areaStartMove = false
}
}
}
}

css部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.main {
position: relative;

.area {
position: absolute;
width: 100px;
height: 40px;
top: 0;
left: 0;
border: 2px dotted #ccc;
border-radius: 2px;
cursor: move;
}
}

参考

MDN-getBoundingClientRect