实现DOM元素拖拽应该考虑以下几个问题
- 监听的鼠标事件
- 拖拽定位父元素的兼容
- 拖拽超出父元素范围的处理
- 鼠标始终定位在被拖拽元素的中心点 - 合理优化
几个【距离】属性
在正式开始写逻辑前,首先要对几个属性要有所了解
-
获取元素的宽/高
offsetWidth/offsetHeight
- 获取元素的宽度/高度(包括边框和内边距)clientWidth/clientHeigh
- 获取元素的宽度/高度(不包括边框)clientLeft/clientTop
- 获取左/上边框大小
-
鼠标位置
clientX/clientY
- 鼠标相对于浏览器窗口左上角为坐标原点,X/Y轴的距离
-
getBoundingClientRect()
- 返回值是一个 [
DOMRect
]对象,是包含整个元素的最小矩形(包括padding
和border-width
)。该对象使用left
、top
、right
、bottom
、x
、y
、width
和height
这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了width
和height
以外的属性是相对于视图窗口的左上角来计算
- 返回值是一个 [
几个【鼠标事件】监听
-
mousedown
- 鼠标【按下】事件
- 在这个事件中进行:
- 去获取子元素的DOMRect
- 开启拖拽标识
-
mouseup
- 鼠标【松开】事件
- 在这个事件中进行:
- 关闭拖拽标识
- 收尾工作
-
mousemove
- 鼠标【移动】事件
- 在这个事件中进行:
- 改变拖拽元素的定位
- 处理拖拽元素超出范围问题
"拖拽对象" - dragDomObj
为了方便属性的调用,创建并暴露一个"拖拽对象" - dragDomObj
。这个对象存储公共属性、入口方法。
js
const dragObjCom = {
dragObj: null, // 被拖拽元素
parentObj: null, // 被拖拽元素的父元素
dragFlag: false, // 拖拽开始的标识
topDistance: 0, // 被拖拽元素的top距离
leftDistance: 0, // 被拖拽元素的left距离
rectOfDragObj: null, // 获取被拖拽DOM的视口边缘距离 top right bottom left
/*
DOM元素拖拽函数封装
@param { object } dragObj 被拖拽元素
@param { object } parentObj 被拖拽元素的父元素
*/
dragDomFunc(dragObj, parentObj = document.body) {
this.dragObj = dragObj
this.parentObj = parentObj
// 监听鼠标按下事件
document.addEventListener('mousedown', event => this.mouseDownforDrag(event))
// 监听鼠标松开事件
document.addEventListener('mouseup', () => this.mouseUpForDrag())
// 监听鼠标移动事件
document.addEventListener('mousemove', event => this.mouseMoveForDrag(event))
}
}
在这里之所以要传入参数parentObj 被拖拽元素的父元素
,是为了兼容父元素是document.body 和自定义父元素两种情况
mouseDownforDrag 的实现
js
// 鼠标按下方法
mouseDownforDrag(event) {
// event不是被拖拽元素时,不作处理
if (event.target !== this.dragObj) return
this.dragFlag = true; // 开始拖拽
if (this.parentObj === document.body) {
this.rectOfDragObj = this.dragObj.getBoundingClientRect()
} else {
// 当自定义传入父元素时,要自主构建一个DOMRect对象
this.rectOfDragObj = this.handleGetBoundingClientRect()
}
},
handleGetBoundingClientRect 的实现
js
// getBoundingClientRect方法改写 - 当父元素不是document.body时
handleGetBoundingClientRect() {
// 获取传入父元素的DOMRect
const parentRectObj = this.parentObj.getBoundingClientRect();
// 创建top/right/bototm/left属性
const top = parentRectObj.top
const right = parentRectObj.right
const bottom = parentRectObj.bottom
const left = parentRectObj.left
const boundingClientRectObj = {
width: this.dragObj.offsetWidth,
height: this.dragObj.offsetHeight,
top,
right,
bottom,
left,
}
return boundingClientRectObj
},
mouseUpForDrag 的实现
js
// 鼠标按下方法
mouseUpForDrag() {
this.dragFlag = false; // 拖拽停止
// 做元素定位左、上是否溢出父元素的范围
if (this.dragObj.style.top.split('px')[0] < 0) this.dragObj.style.top = '0px';
if (this.dragObj.style.left.split('px')[0] < 0) this.dragObj.style.left = '0px';
},
mouseMoveForDrag 的实现
js
// 鼠标移动方法
mouseMoveForDrag(e) {
// 非拖拽状态下直接返回
if (!this.dragFlag) return
// clientX,clientY分别代表鼠标距离屏幕可视区左上角的距离
/*
减去被拖拽元素的宽高的1/2
【目的】在拖动过程中鼠标始终停留在元素的中心位置
【引发的问题】
1. 停止拖动后,若 -top <= -rectOfDragObj.height/2, -left <= -rectOfDragObj.width/2 也识别超出可视区外,所以在监听鼠标松开时做判断
2. 拖动过程,距离右、下距离分别为 rectOfDragObj.width/2、rectOfDragObj.height/2 时,被拖拽元素迅速定位到可视区域的宽高最大值,需要在,移出右、上的判断中减去这部分的值
*/
leftDistance = e.clientX - this.rectOfDragObj.width / 2
topDistance = e.clientY - this.rectOfDragObj.height / 2
// 处理鼠标移出屏幕可视区的判断
let clientWidth = this.parentObj.clientWidth; // 获取父元素的可视宽度
let clientHeight = this.parentObj.clientHeight; // 获取父元素的可视高度
//【下面的判断都加了 this.rectOfDragObj.width(height)/2的判断,是因为上面逻辑把鼠标的位置放到拖拽元素的中心位置】
// 移出左边区域
if (e.clientX - this.rectOfDragObj.width / 2 <= 0) { leftDistance = 0;}
// 移出上边区域
if (e.clientY - this.rectOfDragObj.height / 2 <= 0) { topDistance = 0; }
// 移出右边区域
if (e.clientX + this.rectOfDragObj.width - this.rectOfDragObj.width / 2 > clientWidth) {
leftDistance = clientWidth - this.rectOfDragObj.width;
}
// 移出下边区域
if (e.clientY + this.rectOfDragObj.height - this.rectOfDragObj.height / 2 > clientHeight) {
topDistance = clientHeight - this.rectOfDragObj.height;
}
// 改变元素定位
this.dragObj.style.top = `${topDistance}px`
this.dragObj.style.left = `${leftDistance}px`
}
html
html
<div id="drag_parent">
<div id="drag_dom"></div>
</div>
<style>
body {
height: 100vh;
}
#drag_parent {
position: relative;
width:300px;
height: 200px;
background-color: beige;
}
#drag_dom {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 80px;
background-color:chocolate;
}
</style>