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>