场景描述
今天遇见一个问题,那就是产品希望在弹出来的窗口。 可以移动这个弹窗的位置 增加用户体验,我们直接使用的element-ui中的 Dialog 对话框 我们现在需要拖拽标题,移动元素位置
元素拖拽的思路
要让元素按下移动,我们需要实现以下几个步骤: 1.鼠标按下元素跟随光标移动 2.鼠标抬起元素停止移动 3.移动的区域进行限制(只能在屏幕可视区域内移动-不能产生滚动条) 4.鼠标抬起之后,移除移动和抬起事件 5.处理抬起事件偶尔不会被触发呢?
拖拽的核心示意图以及用到的方法
offsetX:设置或获取鼠标指针位置相对于触发事件对象的x坐标。 offsetY:设置或获取鼠标指针位置相对于触发事件对象的y坐标。 clientX 获取鼠标相对于浏览器左上角x轴的坐标; clientY 获取鼠标相对于浏览器左上角y轴的坐标; window.innerWidth:表示窗口视图区的大小(即视口(viewport)大小而非浏览器窗口大小) window.innerHeight window.innerHeight和innerWidth的大小会根据具体显示器和具体浏览器放大缩小所变化。 它的值仅仅只是当前浏览器窗口大小所对应的宽高,单位是px(像素)。 offsetWidth:返回盒子模型的宽度(包括width+左右padding+左右border) offsetHeight:
鼠标按下元素跟随光标移动
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ padding: 0px; margin: 0px; } .box{ width: 300px; height: 30px; background: pink; position: absolute; left: 0; top: 0; } </style> </head> <body> <div class="box" id="moveElement">我可以移动</div> <script> window.onload = function(){ // 获取元素节点 let moveElement = document.getElementById('moveElement'); // 给元素注册鼠标按下事件 moveElement.onmousedown = function(e){ //兼容 e || window.event 现在都可以 let event = e || window.event; // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离 let point_x=event.offsetX; let point_y=event.offsetY; // 鼠标移动(小方块在文档上移动,给文档注册一个是移动事件) document.onmousemove = function(ent){ let evt = ent || window.event; // 获取鼠标移动的坐标位置 let ele_left= evt.clientX - point_x; let ele_top= evt.clientY - point_y; // 将移动的新的坐标位置进行赋值 moveElement.style.left = ele_left + 'px'; moveElement.style.top = ele_top + 'px' } } } </script> </body> </html>
鼠标抬起元素停止移动
鼠标抬起停止移动的思路是: 我们可以在全局设置一个开关,let flag = false; 当鼠标按下的时候设置为 true moveElement.onmousedown = function(e){ flag = true } 当鼠标抬起的时候设置 flase document.onmouseup = function(event){ flag= false } 在对移动元素赋值的时候,需要进行判断; 只有鼠标按下的状态才可以移动 if(flag){ // 将移动的新的坐标位置进行赋值 moveElement.style.left = ele_left + 'px'; moveElement.style.top = ele_top + 'px' }
为什么我们要对元素移动的区域进行限制了
为什么我们需要对元素的移动区域进行限制? 如果不进行限制。 元素拖拽的时候会产生滚条。用户体验不好。
移动的区域进行限制(不能产生滚动条)
我们来分析一下: 元素在水平反向的取值:最小值0(元素在最左侧); 最大值元素(屏幕窗体innerWidth - 元素宽度offsetWidth); 此时元素紧靠最右侧。这样元素就不会产生滚条。 在垂直方向上的取值:最小值是0(元素在最顶部); 最大值元素(屏幕窗体innerHeight - 元素宽度offsetHeight); 此时元素紧靠最底侧。这样元素就不会产生滚条。
// 给元素注册鼠标按下事件 moveElement.onmousedown = function(e){ flag = true //兼容 e || window.event 现在都可以 let event = e || window.event; // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离 let point_x=event.offsetX; let point_y=event.offsetY; // 鼠标移动(小方块在文档上移动,给文档注册一个是移动事件) document.onmousemove = function(ent){ let evt = ent || window.event; // 获取鼠标移动的坐标位置 let ele_left= evt.clientX - point_x; let ele_top= evt.clientY - point_y; if(ele_left<=0){ // 设置水平方向的最小值 ele_left = 0 }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){ // 设置水平方向的最大值 ele_left = window.innerWidth - moveElement.offsetWidth } if(ele_top<=0){ // 设置垂直方向的最小值 ele_top = 0 }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){ // 设置垂直方向的最大值 ele_top = window.innerHeight - moveElement.offsetHeight } // 只有鼠标按下的状态才可以移动 if(flag){ // 将移动的新的坐标位置进行赋值 moveElement.style.left = ele_left + 'px'; moveElement.style.top = ele_top + 'px' } } }
优化 移动的区域进行限制的代码
我们发现 if(ele_left<=0){ ele_left = 0 }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){ ele_left = window.innerWidth - moveElement.offsetWidth } if(ele_top<=0){ ele_top = 0 }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){ ele_top = window.innerHeight - moveElement.offsetHeight } 这一部分的代码太冗余了,我们可以进行优化一下: 我们发现: 在最最左侧(产生滚东条)的时候元素的坐标位置可能是负数,我们要取最大值。 在最最右侧的时候元素的坐标可能会大于 window.innerWidth - moveElement.offsetWidth 因此我哦们取最小值 垂直方向上同理:我们可以优化为如下 ele_left = Math.min(Math.max(0,ele_left), window.innerWidth - moveElement.offsetWidth) ele_top= Math.min(Math.max(0,ele_top), window.innerHeight - moveElement.offsetHeight)
有bug:鼠标抬起之后,会触发 document.onmousemove事件
我们发现一个问题,在鼠标抬起之后 document.onmousemove = function(ent){} 之中的代码仍然在执行。 为什么鼠标抬起之后仍然会触发 document.onmousemove 之中的代码呢? 因为:在一开始,按下抬起移动事件结束注册后(鼠标抬起,移动事件就应该销毁) 但是我们并没有做销毁处理。
鼠标抬起之后,移除移动事件与抬起事件
// 抬起停止移动 document.onmouseup = function(event){ // 移除移动和抬起事件 this.onmousemove = null; this.onmouseup = null } 这样一来我们就不需要flag这个开关了。 就可以删除开关部分的代码了
又又出现bug:有些时候抬起事件没有被触发
有些时候抬起事件不会被触发: 鼠标按下左键移动,与此同时按下右键移动元素后。 然后左右键松开,这个时候会出现菜单点击空白处。 在这个过程中你会出现(鼠标选中了其他的元素,或者元素中的文字) 这个时候元素就100%不会触发mouseup事件。
为什么选择文字抬起事件就不会被触发呢?
其实抬起事件 mouseup 并没有失效, 而是我们拖动时,鼠标选中了其他的元素或者选中了选中元素中的文字。 这个时候鼠标即使松开。 浏览器内部还是认为用户在复制文字,鼠标还是按下的状态,所以不会触发mouseup事件。 我们知道了为什么不会触发 mouseup 解决办法就非常简单了。 第一种方式:使用css 禁用选中文字 第二种方式:ondragstart 和 ondragend 来处理
第1种方式:css 禁用选中处理抬起事件偶尔不会触发
.box{ width: 300px; height: 30px; background: pink; position: absolute; left: 0; top: 0; -webkit-user-select: none; -moz-user-select: none; -o-user-select: none; user-select: none; }
第2种方式: ondragstart 和 ondragend 来处理
// 解决有些时候,在鼠标松开的时候,元素仍然可以拖动; document.ondragstart = function(ev) { ev.preventDefault(); } document.ondragend = function(ev) { ev.preventDefault(); }
全部代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *{ padding: 0px; margin: 0px; } .box{ width: 300px; height: 30px; background: pink; position: absolute; left: 0; top: 0; } .box:hover{ cursor: move; } </style> </head> <body> <div class="box" id="moveElement">我可以移动</div> <script> window.onload = function(){ // 获取元素节点 let moveElement = document.getElementById('moveElement'); // 给元素注册鼠标按下事件 moveElement.onmousedown = function(e){ //兼容 e || window.event 现在都可以 let event = e || window.event; // 获取鼠标按下去的那一个点距离边框顶部和左侧的距离 let point_x=event.offsetX; let point_y=event.offsetY; // 鼠标移动(小方块在文档上移动,给文档注册一个是移动事件) document.onmousemove = function(ent){ let evt = ent || window.event; // 获取鼠标移动的坐标位置 let ele_left= evt.clientX - point_x; let ele_top= evt.clientY - point_y; // ----冗余代码--- // if(ele_left<=0){ // // 设置水平方向的最小值 // ele_left = 0 // }else if(ele_left >= window.innerWidth - moveElement.offsetWidth){ // // 设置水平方向的最大值 // ele_left = window.innerWidth - moveElement.offsetWidth // } // if(ele_top<=0){ // // 设置垂直方向的最小值 // ele_top = 0 // }else if(ele_top >= window.innerHeight - moveElement.offsetHeight){ // // 设置垂直方向的最大值 // ele_top = window.innerHeight - moveElement.offsetHeight // } // 优化为下面的 ele_left = Math.min(Math.max(0,ele_left), window.innerWidth - moveElement.offsetWidth) ele_top= Math.min(Math.max(0,ele_top), window.innerHeight - moveElement.offsetHeight) moveElement.style.left = ele_left + 'px'; moveElement.style.top = ele_top + 'px' } // 抬起停止移动 document.onmouseup = function(event){ console.log("抬起停止移动" ) // 移除移动和抬起事件 this.onmouseup = null; this.onmousemove = null; //修复低版本的ie可能出现的bug if(typeof moveElement.releaseCapture!='undefined'){ moveElement.releaseCapture(); } } // 解决有些时候,在鼠标松开的时候,元素仍然可以拖动-使用的是第二种方式 document.ondragstart = function(ev) { ev.preventDefault(); } document.ondragend = function(ev) { ev.preventDefault(); } } } </script> </body> </html>
需要注意的是:
我们的onmousemove 移动事件, onmouseup抬起事件 都必须要放置在 moveElement.onmousedown 的里面, 否者会出现 鼠标抬起后,元素仍在在移动