一般来说,实现拖拽的话,拖拽的基本功能是你对某个想要移动的元素进行点击,点击后按住不放进行移动,该元素就会跟着你的鼠标移动,当你把鼠标一松开,则该元素就会定格在那里。一般的实现这个功能要注意两个地方,一是该元素必须是绝对定位,因为绝对定位脱离了文档流,才能够随意的移动;二是如何能够很好的获取移动的坐标,我们获取的坐标是鼠标移动的坐标减去鼠标在该元素距离边框的坐标,比如说我们点击了该元素的中心位置,不管怎么拖动,我们的鼠标所在的位置还是在该元素的中心位置。所以下面的代码表现的是这两个基本的东西:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset='utf-8'>
<style type="text/css">
#div{
width: 200px;
height: 200px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<div id='div'></div>
</body>
<script type="text/javascript">
var a = document.getElementById('div')
a.onmousedown = function(ev) {
var oEvent = ev || event
var disX = oEvent.clientX - a.offsetLeft
var disY = oEvent.clientY - a.offsetTop
a.onmousemove = function(ev) {
var oEvent = ev || event
var l = oEvent.clientX - disX
var t = oEvent.clientY - disY
a.style.left = l + 'px'
a.style.top = t + 'px'
}
a.onmouseup = function() {
a.onmousemove = null
a.onmouseup = null
}
}
</script>
</html>
上面代码中,disX和disY表示的是鼠标距离该元素边框的距离,如下图:
上面的图中,黑色边框表示的是我们的页面,在页面里有红色的边框,这个是我们的一个要拖拽的div,里面的红点表示的是我们点击按住的坐标。而我们的disX的长度就是红色线所指明的长度,黑色线表示的是鼠标距离可视页面的距离,想要求出disX就要把这个黑色线clientWidth减去我们红色边框距离页面左端的距离。而下面的l和t变量,则是让红色div移动的坐标,这个坐标的值的计算是鼠标所在的可视区的坐标减去disX和disY。可能你会有疑问,说既然disX=oEvent.client-a.offsetLeft,而l却等于oEvent.clientX-disX,那么可不可以直接把disX的表达式代入到l中即得:a.offsetLeft呢?显然如果你试过的话这是不可能的,因为根本上是鼠标坐标发生变化进而才导致了整个div距离页面的left和top也发生了变化,如果脱离了鼠标的坐标,那么这个表达式的值是不变的,也就是说不会发生移动的,这个要注意。最后面onmouseup函数里是结束掉onmousemove和onmouseup函数
可能你试了上面的代码后发现如果鼠标移动的很快的时候,鼠标飞出了div的区域导致了div不会移动了。解决这个办法就需要当鼠标点击了后,onmousemove和onmouseup作用的对象是document而不是我们的div元素,修改后的js代码如下:
var a = document.getElementById('div')
a.onmousedown = function(ev) {
var oEvent = ev || event
var disX = oEvent.clientX - a.offsetLeft
var disY = oEvent.clientY - a.offsetTop
document.onmousemove = function(ev) {
var oEvent = ev || event
var l = oEvent.clientX - disX
var t = oEvent.clientY - disY
a.style.left = l + 'px'
a.style.top = t + 'px'
}
document.onmouseup = function() {
document.onmousemove = null
document.onmouseup = null
}
}
但是如果你把窗口缩小化,再来试一下现在这个代码,如果你的鼠标移到浏览器外,会发现另一个问题,当鼠标松开后我们找不回来div元素了,这个时候我们就需要对可视区的边界进行判断,当鼠标移过可视区,则返回一个边界值,代码如下:
var a = document.getElementById('div')
a.onmousedown = function(ev) {
var oEvent = ev || event
var disX = oEvent.clientX - a.offsetLeft
var disY = oEvent.clientY - a.offsetTop
document.onmousemove = function(ev) {
var oEvent = ev || event
var l = oEvent.clientX - disX
var t = oEvent.clientY - disY
if(l < 0) {
l = 0
}
else if (l > document.documentElement.clientWidth - a.offsetWidth) {
l = document.documentElement.clientWidth - a.offsetWidth
};
if(t < 0) {
t = 0
}
else if (t > document.documentElement.clientHeight - a.offsetHeight) {
t = document.documentElement.clientHeight - a.offsetHeight
};
a.style.left = l + 'px'
a.style.top = t + 'px'
}
document.onmouseup = function() {
document.onmousemove = null
document.onmouseup = null
}
}
最后如果你用的是旧版本的火狐浏览器,当你拖拽div的时候,有时候你会发现你拖拽的时候会拖拽出一个图层链接出来,解决这个问题的办法是在onmousemove方法里的最后加入return false,这个会禁止掉浏览器默认的行为,可以修复这个bug。
不过如果在我们的div里加入文字的话,如:
<body> afdsfasfsdfdadfasfs<br>
<div id='div'>adfasfdasdfasdf</div>
agasdfafawgfwgwggw
</body>
运行这段代码后我们发现,在ie浏览器里特别是ie8及之前的浏览器,当你点着有文字部分的div进行拖拽的时候,一不小心就对文字进行选中了,其他浏览器的解决办法是通过return false,而如果是ie浏览器的话,我们则要使用ie浏览器特有的一个功能:事件捕获函数setCapture和解除绑定的releaseCapture函数。如下代码:
var a = document.getElementById('div')
a.onmousedown = function(ev) {
var oEvent = ev || event
var disX = oEvent.clientX - a.offsetLeft
var disY = oEvent.clientY - a.offsetTop
if (a.setCapture) {
a.onmousemove = moveArea
a.onmouseup = upArea
a.setCapture()
} else{
document.onmousemove = moveArea
document.onmouseup = upArea
}
function moveArea(ev) {
var oEvent = ev || event
var l = oEvent.clientX - disX
var t = oEvent.clientY - disY
// 不会超出可视区
if(l < 0) {
l = 0
}
else if (l > document.documentElement.clientWidth - a.offsetWidth) {
l = document.documentElement.clientWidth - a.offsetWidth
};
if(t < 0) {
t = 0
}
else if (t > document.documentElement.clientHeight - a.offsetHeight) {
t = document.documentElement.clientHeight - a.offsetHeight
};
a.style.left = l + 'px'
a.style.top = t + 'px'
}
//防止鼠标移动过快
function upArea() {
this.onmousemove = null
this.onmouseup = null
if (a.releaseCapture) {
a.releaseCapture()
};
}
return false
}
被setCapture定义的对象不管你作用到哪里,如上面的代码如果去掉releaseCapture,不管我的鼠标在浏览器哪里点击,只要一拖拽那个div就会跟着移动,这时候setCapture是将所有对象的事件都集中到某个元素对象的事件上,如果你想要可以操作别的东西,这个时候就需要releaseCapture来清除这个事件的捕获,当鼠标一松开就会被清除事件的捕获。
除此以外,因为setCapture在除了ie6到8的浏览器能用外其他都不能用,所以一开始来个兼容性判断,之后对某个浏览器进行符合他们的要求进行操作,并将通用的代码合并到一个方法里,提高重用性。