现在我们的类就可以运作了,但正如你们所看到的那样,当鼠标拖动太快会出现鼠标移出div的情况。这是因为移动得越快,位移的距离就越大,拖动元素一下子从我们的鼠标溜走了,就无法调用mouseup事件。在IE中我们可以利用setCapture()来补救,但一旦某个元素调用setCapture(),文档中所有后续的鼠标事件都会在冒泡之前传到该元素,直到调用了releaseCapture()。换言之,在完成这些鼠标事件之前,它是不执行其他事件,一直占着线程,于是出现了我们的光标离开拖动元素的上方也能拖动元素的怪异现象。
你在拖动块上点一下,然后再到拖动块外面点一下,就可以实现"隔空拖动"的神奇效果了!(当然只限IE)
由于鼠标事件一直接着线程,所以在我们不用的时候,一定要releaseCapture()来解放它。
在firefox中我们可以使用window.captureEvents(),火狐说这方法已经废弃,但我怎么在各标准浏览器中运作良好呢?!不过不管怎么样,来来回回要设置捕获与取消捕获非常麻烦与吃内存,我们需要转换思路。因为如果鼠标离开拖动元素上方,我们的绑定函数就无法运作,要是把它们绑定在document上呢,鼠标就无论在何处都能监听拖动元素。但触发拖动的onmousedown事件我们还保留在拖动元素上,这事件不会因为不执行就引起差错之虞。不过,由于绑定对象一变,我们要在这些事件中获得拖动对象的引用的难度就陡然加大,这里我就直接把它们做成构造函数内的私有函数吧。
01.
var
Drag =
function
(id){
02.
var
el = document.getElementById(id);
03.
el.style.position =
"absolute"
;
04.
var
drag =
function
(e) {
05.
e = e || window.event;
06.
el.style.cursor =
"pointer"
;
07.
!+
"\v1"
? document.selection.empty() : window.getSelection().removeAllRanges();
08.
el.style.left = e.clientX - el.offset_x +
"px"
;
09.
el.style.top = e.clientY - el.offset_y +
"px"
;
10.
el.innerHTML = parseInt(el.style.left,10)+
"X"
+parseInt(el.style.top,10);
11.
}
12.
13.
var
dragend =
function
(){
14.
document.onmouseup =
null
;
15.
document.onmousemove =
null
;
16.
}
17.
18.
var
dragstart =
function
(e){
19.
e = e || window.event;
20.
el.offset_x = e.clientX - el.offsetLeft;
21.
el.offset_y = e.clientY - el.offsetTop;
22.
document.onmouseup = dragend;
23.
document.onmousemove = drag;
24.
return
false
;
25.
}
26.
el.onmousedown = dragstart;
27.
}
进一步改进,不使用mouseup事件,这样就减少了错过mouseup事件带来的鼠标粘着卡壳的问题。但由于标准浏览器不会自动切换e.button和e.which的值,我被迫动用一个功能键Shirt来停止鼠标拖拽。
01.
var
Drag =
function
(id){
02.
var
el = document.getElementById(id);
03.
el.style.position =
"absolute"
;
04.
var
drag =
function
(e){
05.
var
e = e || window.event,
06.
button = e.button || e.which;
07.
if
(button == 1 && e.shiftKey ==
false
){
08.
el.style.cursor =
"pointer"
;
09.
!+
"\v1"
? document.selection.empty() : window.getSelection().removeAllRanges();
10.
el.style.left = e.clientX - el.offset_x +
"px"
;
11.
el.style.top = e.clientY - el.offset_y +
"px"
;
12.
el.innerHTML = parseInt(el.style.left,10)+
" x "
+parseInt(el.style.top,10);
13.
}
else
{
14.
document.onmousemove =
null
;
15.
}
16.
}
17.
var
dragstart =
function
(e){
18.
e = e || window.event;
19.
el.offset_x = e.clientX - el.offsetLeft;
20.
el.offset_y = e.clientY - el.offsetTop;
21.
el.style.zIndex = ++Drag.z;
22.
document.onmousemove = drag;
23.
return
false
;
24.
}
25.
Drag.z = 999;
26.
el.onmousedown = dragstart;
27.
}
虽然不绑定mouseup的确在IE爽到high起,但在火狐等浏览要多按一个键才能终止拖动,怎么说也对用户体验造成影响,因此当一种知识学学就算了。我们还是选择方案2。
接着下来我们为方案扩展一下功能。首先范围拖动,就是存在一个让它在上面拖动的容器。如果存在容器,我们就取得其容器的四个点的坐标,与拖动元素的四个点的坐标比较,从而修正top与left。由于我已经实现了getCoords函数,取得页面上某一点的坐标易如反掌。同时这样做,我们就不用把此容器设置成offsetParent。总之,多一事不如少一事。
01.
var
getCoords =
function
(el){
02.
var
box = el.getBoundingClientRect(),
03.
doc = el.ownerDocument,
04.
body = doc.body,
05.
html = doc.documentElement,
06.
clientTop = html.clientTop || body.clientTop || 0,
07.
clientLeft = html.clientLeft || body.clientLeft || 0,
08.
top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop ) - clientTop,
09.
left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft
10.
return
{
'top'
: top,
'left'
: left };
11.
};
接着我们要取得容器的四个点的坐标:
1.
_cCoords = getCoords(container),
2.
_cLeft = _cCoords.left,
3.
_cTop = _cCoords.top,
4.
_cRight = _cLeft + container.clientWidth,
5.
_cBottom = _cTop + container.clientHeight;
接着是拖动元素的四个点的坐标:
1.
var
_left = el.offsetLeft,
2.
_top = el.offsetTop,
3.
_right = _left + el.offsetWidth,
4.
_bottom = _top + el.offsetHeight,
但是这样取得的是没有拖动前的坐标,拖动时的坐标在上面的方案2也已给出了:
1.
var
_left = e.clientX - el.offset_x,
2.
_top = e.clientY - el.offset_y,
修正坐标很简单,如果拖动元素在左边或上边超出容器,就让拖动元素的top与left取容器的_cLeft或_cTop值,如果从右边超出容器,我们用_cRight减去容器的宽度再赋给拖动元素的top,下边的处理相仿。
01.
if
(_left < _cLeft){
02.
_left = _cLeft
03.
}
04.
if
(_top < _cTop){
05.
_top = _cTop
06.
}
07.
if
(_right > _cRight){
08.
_left = _cRight - el.offsetWidth;
09.
}
10.
if
(_bottom > _cBottom){
11.
_top = _cBottom - el.offsetHeight;
12.
}
13.
el.style.left = _left +
"px"
;
14.
el.style.top = _top +
"px"
;
水平锁定与垂直锁直也是两个很常用的功能,我们也来实现它。原理很简单,在开始拖动时就把top与left保存起来,待到拖动时再赋给它就行了。代码请直接看运行框的代码: