拖拽,在网页中是一个很常见的 js 特效,在很多场景中都可以使用。本文中将总结拖拽的实现原理和注意事项。
拖拽的原理
我们在使用我们的操作系统的时候,经常用鼠标拖动电脑屏幕上的窗口。我们回忆一下我们拖动屏幕上的窗口的是怎么操作的。首先把把鼠标移动到需要拖动的窗口上面,按下鼠标,然后移动鼠标(这个时候窗口在不停地重绘),就可以发现窗口在我们鼠标的操控之下了,鼠标松开的时候停止拖动。我们在拖拽鼠标的过程中,你会发现从你按下鼠标的时候开始,在拖动的过程中,鼠标相对于被拖动元素的位置是不变的。我们在浏览器中要实现一个元素的拖动效果,原理是一样的。为了更好的理解这个问题,请看下面这张图。
这张图中有3个大的框,灰色的框表示我们的浏览器窗口,黑色的表示一个大 dom 元素 div,黑色的框中有两个蓝色的dom,第一个蓝色的元素表示初始的位置,第二个蓝色的框表示拖动后的位置。上面的小圆圈表示鼠标的位置。我们发现从第一个位置,拖动到另一个位置,x1(鼠标相对于拖动元素的位置) 的值是不变的。于是我们就可以在鼠标按下的时候记录鼠标相对于拖动元素的位置,于是在拖动的时候就可以根据鼠标的位置和这个值计算出元素新的位置。
根据上图可以推算出拖动时元素相对于元素的定位元素(offsetParent)的位置。
// 拖动前
x1 = x30-X20-x4;
// 拖动后
x1 = x31 -x21 - x4;
==>
x30 - x20 -x4 = x31 - x21 - x4;
x21 = x31 - x30 + x20;
// 其中x20可以通过在点击的时候计算得出
// ele 表示被拖动的元素
x20 = ele.offsetLeft;
拖动demo
在了解了代拖动原理后,再看看一个例子吧。演示地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拖动示例</title>
<style>
#drag {
width: 200px;
height: 200px;
background-color: #eee;
position: absolute;
}
</style>
</head>
<body>
<div id="drag"></div>
<script>
// 被拖动的那个元素
var dragEle = document.getElementById('drag');
// 给元素添加mousedown事件
dragEle.onmousedown = function(e){
e = e || event;
// 阻止默认事件行为
e.preventDefault();
var x1 = e.clientX;
var y1 = e.clientY;
var l1 = dragEle.offsetLeft;
var t1 = dragEle.offsetTop;
// 鼠标按下后给 document 添加 mousemove 事件
document.onmousemove = function(e) {
e = e || event;
var l = e.clientX - x1 + l1;
var t = e.clientY - y1 + t1;
// 让元素不被拖出浏览器
if(l < 0 ) {
l = 0;
} else if( l > document.documentElement.clientWidth - dragEle.offsetWidth) {
l = document.documentElement.clientWidth - dragEle.offsetWidth;
}
if( t < 0 ) {
t = 0;
} else if (t > document.documentElement.clientHeight - dragEle.offsetHeight) {
t = document.documentElement.clientHeight - dragEle.offsetHeight;
}
dragEle.style.left = l + "px";
dragEle.style.top = t + "px";
}
// 同时也给 document 添加 mouseup 事件
// 鼠标松开的时候 移除添加在 document 上的事件
document.onmouseup = function(){
document.onmousemove = null;
document.onmouseup = null;
}
};
</script>
</body>
</html>
看上面的代码发现,总共添加了3个事件mousedown
, mousemove
, mouseup
.
从代码中可以发现在给被拖动的元素添加了mousedown
。在鼠标按下的时候,给document添加mousemove,mouseup事件。那么为什么要把 mousemove 和 mouseup 事件在document 上面呢,而不是被拖动的元素上面。因为如果被拖动的元素比较小时,是鼠标移动的时候很容易就移出被拖动元素的外面了,就拖不动了。但是 document 就不一样了,它和浏览器窗口的大小一样。在鼠标松开的时候清楚了加在document上的 mousemove, mouseup事件。在鼠标按下的时候,我们用preventDefault()阻止了浏览器的默认事件行为,为什么呢?因为在按下鼠标的时再移动鼠标会触发拖动选择行为。