js实现页面元素的拖拽
平时在我们页面上,经常会悬浮着一些功能按钮,如帮助,联系客服等,按钮的显示比较简单,用定位悬浮在自己需要的位置上就行,比如下面的页面上我们展示一个帮助的按钮,用户点击后可以展示一些帮助的信息:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<style type="text/css">
.help-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: blue;
color: #fff;
position: fixed;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
right: 0;
bottom: 150px;
}
</style>
<title>js实现拖拽</title>
</head>
<body>
<div class="help-btn" id="help-btn">帮助</div>
</body>
</html>
但是这样定位一般是固定在这个位置,如果这时候刚好【帮助按钮】挡住了这个位置原本有的其他操作按钮,那么我们就需要能够把【帮助按钮】拖开,不影响原本页面上的操作。
一般实现拖动的思路是:在【帮助按钮】上鼠标按下时,记录下当前的坐标,以及原本相对于父元素的位置,然后监听【帮助按钮】的鼠标移动事件【onmousemove】,然后取现在的坐标,计算出两轴上移动的距离,用原本的位置减去移动的位置得到现在的位置,再重新设置给【帮助按钮】即可。最后在【帮助按钮】鼠标抬起事件【onmouseup】上注销对鼠标移动的监听即可。
代码:
<script type="text/javascript">
let btnEle = document.getElementById("help-btn");
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
let defaultX = e.clientX; //默认位置的x轴坐标
let defaultY = e.clientY; //默认位置的y轴坐标
let defaultLeft = btnEle.offsetLeft; //默认左侧偏移位置
let defaultTop = btnEle.offsetTop; //默认顶部偏移位置
//帮助按钮鼠标移动时
btnEle.onmousemove = (me) => {
btnEle.style.cursor = "move"; //修改鼠标样式
let nowX = me.clientX; //当前位置的x轴坐标
let nowY = me.clientY; //当前位置的y轴坐标
let moveX = defaultX - nowX; //在x轴上的移动距离
let moveY = defaultY - nowY; //在y轴上移动的距离
let nowLeft = defaultLeft - moveX; //当前位置左侧偏移量
let nowTop = defaultTop - moveY; //当前位置顶部偏移量
btnEle.style.left = `${nowLeft}px`; //将当前位置赋值给帮助按钮
btnEle.style.top = `${nowTop}px`;
};
//鼠标抬起时
btnEle.onmouseup = () => {
btnEle.style.cursor = "default"; //重置鼠标样式
//清除事件
btnEle.onmousemove = null;
btnEle.onmouseup = null;
};
};
</script>
至此可以实现拖动【帮助按钮】移动位置。但是会出现以下几个问题:
问题1:移动过快,鼠标超过【帮助按钮】的范围,会导致鼠标抬起事件失效,造成移动卡顿;
解决方法,将鼠标移动事件和鼠标抬起事件挂载在document上。
代码:
<script type="text/javascript">
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
...
//鼠标移动时
document.onmousemove = (me) => {
...
};
//鼠标抬起时
document.onmouseup = () => {
...
//清除事件
document.onmousemove = null;
document.onmouseup = null;
};
};
</script>
问题2:鼠标移动过快,可能会选中“帮助”文字,这时候会造成鼠标抬起事件失效。出现移动bug。
解决方法:在移动时禁用文字选中。鼠标抬起时还原
代码:
<script type="text/javascript">
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
...
//禁用文字选中
document.onselectstart = () => {
return false;
};
//鼠标移动时
...
//鼠标抬起时
document.onmouseup = () => {
...
document.onselectstart = null;
};
};
</script>
问题3:会移动到屏幕外面去。
解决方法:限制范围不超过页面区域(或者你自定义父容器的内容范围)。
代码:
<script type="text/javascript">
...
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
...
let pageWidth = window.innerWidth; //页面宽度
let pageHeight = window.innerHeight; //页面高度
...
//鼠标移动时
document.onmousemove = (me) => {
...
//对位置进行限定,只显示在设定的区域内
nowLeft = nowLeft <= 0 ? 0 : nowLeft;
nowLeft =
nowLeft >= pageWidth - btnEle.offsetWidth
? pageWidth - btnEle.offsetWidth
: nowLeft;
nowTop = nowTop <= 0 ? 0 : nowTop;
nowTop =
nowTop >= pageHeight - btnEle.offsetHeight
? pageHeight - btnEle.offsetHeight
: nowTop;
...
};
...
};
</script>
问题4:拖动的时候也会触发【帮助按钮】的点击事件。
解决方法:在鼠标按下时,记录当前时间,在鼠标抬起时再记录一次时间。在点击事件方法中对两个时间差值进行判断,此处我设定是100,可根据自己需求调整。大于这个时间间隔则为拖拽,不继续执行点击的方法。
代码:
<script type="text/javascript">
...
let startTime = 0; //鼠标点击时间
let endTime = 0; //鼠标抬起时间
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
startTime = new Date().getTime();
...
//鼠标抬起时
document.onmouseup = () => {
endTime = new Date().getTime();
...
};
};
//按钮的点击事件
btnEle.onclick = () => {
if (endTime - startTime >= 100) return false; //说明是拖拽
console.log("我被点击了!");
};
</script>
到这里为止在单页面上可以实现元素的拖动。但是如果页面上还存在iframe窗口,当鼠标经过iframe窗口的时候,会出现拖拽卡顿,鼠标抬起事件失效的问题。
解决思路,在鼠标点击的时候在页面上加上一个蒙层,把蒙层作为父元素进行监听。鼠标移除的时候,去除蒙层。
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<style type="text/css">
.help-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: blue;
color: #fff;
position: fixed;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
right: 0;
bottom: 150px;
z-index: 9999;
}
.move-modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
}
</style>
<title>js实现拖拽</title>
</head>
<body>
<!-- 蒙层 -->
<div id="move-modal"></div>
<div class="help-btn" id="help-btn">帮助</div>
</body>
<script type="text/javascript">
let btnEle = document.getElementById("help-btn");
let startTime = 0; //鼠标点击时间
let endTime = 0; //鼠标抬起时间
//帮助按钮鼠标按下时
btnEle.onmousedown = (e) => {
startTime = new Date().getTime();
let fatherModal =
document.getElementById("move-modal"); //拿到蒙层元素
fatherModal.classList.add("move-modal"); //给页面加上蒙层
let defaultX = e.clientX; //默认位置的x轴坐标
let defaultY = e.clientY; //默认位置的y轴坐标
let defaultLeft = btnEle.offsetLeft; //默认左侧偏移位置
let defaultTop = btnEle.offsetTop; //默认顶部偏移位置
let pageWidth = fatherModal.offsetWidth; //页面宽度
let pageHeight = fatherModal.offsetHeight; //页面高度
//禁用文字选中
window.onselectstart = () => {
return false;
};
//鼠标移动时
fatherModal.onmousemove = (me) => {
btnEle.style.cursor = "move"; //修改鼠标样式
let nowX = me.clientX; //当前位置的x轴坐标
let nowY = me.clientY; //当前位置的y轴坐标
let moveX = defaultX - nowX; //在x轴上的移动距离
let moveY = defaultY - nowY; //在y轴上移动的距离
let nowLeft = defaultLeft - moveX; //当前位置左侧偏移量
let nowTop = defaultTop - moveY; //当前位置顶部偏移量
//对位置进行限定,只显示在设定的区域内
nowLeft = nowLeft <= 0 ? 0 : nowLeft; //不能超过左侧
nowLeft =
nowLeft >= pageWidth - btnEle.offsetWidth
? pageWidth - btnEle.offsetWidth
: nowLeft; //不能超过右侧
nowTop = nowTop <= 0 ? 0 : nowTop; //不能超过顶部
nowTop =
nowTop >= pageHeight - btnEle.offsetHeight
? pageHeight - btnEle.offsetHeight
: nowTop; //不能超过底部
//将当前位置赋值给帮助按钮
btnEle.style.left = `${nowLeft}px`;
btnEle.style.top = `${nowTop}px`;
};
//鼠标抬起时,此处必须是document,不然当多屏幕的时候,鼠标移出当前屏幕还是不会触发鼠标抬起
document.onmouseup = () => {
endTime = new Date().getTime();
btnEle.style.cursor = "default"; //重置鼠标样式
fatherModal.classList.remove("move-modal"); //给页面去除蒙层
//清除事件
fatherModal.onmousemove = null;
document.onmouseup = null;
window.onselectstart = null;
};
};
//按钮的点击事件
btnEle.onclick = () => {
if (endTime - startTime >= 100) return false; //说明是拖拽
console.log("我被点击了!");
};
</script>
</html>