完成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
。这个目标存储公共特点、进口办法。
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 的完成
// 鼠标按下办法
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 的完成
// 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 的完成
// 鼠标按下办法
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 的完成
// 鼠标移动办法
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
<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>
成果展现
代码参考
- GitLab: drag-dom-project/index.html main Vincent1900 / routine-program GitLab