前言
项目github地址
最近项目需要实现一个弹框拖拽的功能,本来多快好省的方案就是使用antd的弹框,再配合gayhub上找的拖拽插件来实现。
但过程中遇到一些麻烦,而且用别的东西一时爽,要实现个性化或者改一些东西,需要看别人的源码,会极其的麻烦。
这时,固执的程序员就会抛下一切,全程自己开发,比如我。正所谓求人不如求己,自己动手,丰衣足食。
自己开发,时间可能会多花一些,但收益也是有目共睹的:
1.风险可控,出了问题能快速定位并解决(毕竟是亲手制作);
2.能最大程度实现个性化,比如增加或者减少一些功能;
3.杜绝知其然而不知其所以然。通过手把手搭建项目,实现功能,深入最底层理解逻辑,学习成长。有时还会有意外收获,学到项目之外的知识;
4.知道该功能有哪些坑,然后通过自己尝试或者观摩成熟解决方案来克服,避免以后遇到类似的坑;
5.自己做出来后,能与同事或者小组甚至上传到gayhub和所有开发者共享,为社区做出自己的贡献,收获成就感。久而久之,就成为该细分领域的专家,所有人有问题都来找你,那是何等的荣誉!
。。。
位置参数科普
想开发拖拽功能首先得理解各种位置参数,如图,大概分为这几类:
event.clientX、event.clientY
鼠标相对于浏览器窗口可视区域的X,Y坐标(窗口坐标),可视区域不包括工具栏和滚动条。IE事件和标准事件都定义了这2个属性
event.pageX、event.pageY
类似于event.clientX、event.clientY,但它们使用的是文档坐标而非窗口坐标。这2个属性不是标准属性,但得到了广泛支持。IE事件中没有这2个属性。
event.offsetX、event.offsetY
鼠标相对于事件源元素(srcElement)的X,Y坐标,只有IE事件有这2个属性,标准事件没有对应的属性。
event.screenX、event.screenY
鼠标相对于用户显示器屏幕左上角的X,Y坐标。标准事件和IE事件都定义了这2个属性
clientWidth、clientHeight
返回元素的宽高(包括元素宽高、内边距,不包括边框和外边距)
offsetWidth、offsetHeight
返回元素的宽高(包括元素宽高、内边距和边框,不包括外边距)
功能介绍及思路
基本功能很简单,就是点击弹框header部分按住进行拖动,弹框能跟着鼠标移动,松开则停止。左上角为鼠标坐标与弹框左上角坐标。
细心的读者可以发现,两个坐标有一个差值。恭喜你发现了彩蛋,这个差值就是实现拖拽功能的核心!
鼠标移动时我们能随时获取其位置,我们还能设置弹框左上角的位置,这两者是需要变化的值,同时,前者决定后者,后者跟随前者变化。
那么,二者存在怎样的关系?我们需要一个不变的值,正如数学公式中的常量或系数一样。
没错,就是前面提到的差值。我们发现,拖拽进行时,弹框左上角和鼠标的相对位置是不变的!
顺藤摸瓜,也就是说,只要获取鼠标点击header时,其相对弹框左上角的坐标差,以此为常量,来控制弹框位置就可以了!
另外还有一些优化,比如边界控制、位置修正等,详情见源码。
代码实现
Talk is cheap.Show me the code!
// 获取鼠标点击title时的坐标、title的坐标以及两者的位移
getPosition (e) {
// 标题DOM元素titleDom
const titleDom = e.target
// titleDom的坐标(视窗)
const X = titleDom.getBoundingClientRect().left
// 由于Y轴出现滚动条,需要与鼠标保持一致,存储页面相对位置
const Y = document.getElementsByClassName('group')[0].offsetTop
// 鼠标点击的坐标(页面)
let mouseX = e.pageX
let mouseY = e.screenY
// 鼠标点击位置与modal的位移
const diffX = mouseX - X
const diffY = mouseY - Y
return {X, Y, mouseX, mouseY, diffX, diffY}
}
/**
* 鼠标按下,设置modal状态为可移动,并注册鼠标移动事件
* 计算鼠标按下时,指针所在位置与modal位置以及两者的差值
**/
onMouseDown (e) {
const position = this.getPosition(e)
window.onmousemove = this.onMouseMove
window.onmouseup = this.onMouseUp
this.setState({moving: true, diffX: position.diffX, diffY: position.diffY})
}
// 松开鼠标,设置modal状态为不可移动
onMouseUp (e) {
const { moving } = this.state
moving && this.setState({moving: false});
}
// 鼠标移动重新设置modal的位置
onMouseMove (e) {
const {moving, diffX, diffY} = this.state
if (moving) {
// 获取鼠标位置数据
const position = this.getPosition(e)
// 计算modal应该随鼠标移动到的坐标
const x = position.mouseX - diffX
const y = position.mouseY - diffY
// 窗口大小,结构限制,需要做调整,减去侧边栏宽度
const { clientWidth, clientHeight } = document.documentElement
const modal = document.getElementsByClassName("group")[0]
if (modal) {
// 计算modal坐标的最大值
const maxHeight = clientHeight - modal.offsetHeight
const maxWidth = clientWidth - modal.offsetWidth
// 判断得出modal的最终位置,不得超出浏览器可见窗口
const left = x > 0 ? (x < maxWidth ? x : maxWidth) : 0
const top = y > 0 ? (y < maxHeight ? y : maxHeight) : 0
this.setState({pageX: left, pageY: top})
}
}
}