DOM编程-拖拽(拖动)层的几个细节问题

此文也可以命名为好的拖动层必须做的几个细节

在一个拖拽系统中,做出来容易细节,细节却并不尽如人意..拖拽中没有一个完美(或者说比较完美)的解决方案,原因在于:

我们所知道拖拽的实现的方法(cross-browser)是有三个事件的:onmousedown,onmousemove,onmouseup,
即onmousedown来捕获,onmousemove拖拽,onmouseup释放事件. 这在一般情况下是没有问题的.在此种情况下会出现事件没有释放------当鼠标按下(onmousedown)移动某element(onmousemove)到IE(或其它浏览器)窗体的时候再松开鼠标,这个时候onmouseup并没有释放事件,你再将鼠标移入浏览器窗体时,element会拖动. 此细节可描述为onmouseup在浏览器外的时候,浏览器无法触发该事件.

今天和JK讨论这个问题的时候, 有了一个解决方法, 在这里记录也算是一个自己的笔记. 问题2以后再写了,如果你有比较好的解决方案,麻烦将思路Email一份给我,Thnaks:) blueDestiny[at]126.com, 请将[at]替换成@.

首先, 请点击以下链接,按照问题描述的那样操作,看看是否属实:
http://www.never-online.net/code/js/dragdemo/

解决方案:
IE下,可以采用setCapture()方法,在onmousedown时捕获,在onmouseup时释放, 由此可以推出setCapture是"全局"(这个全局不是指全局变量中的全局,而是在整个窗体区域)的.看MS给的例子:
+展开
-HTML
<BODY onload="oOwnCapture.setCapture()"
onclick="document.releaseCapture()">

<DIV ID=oOwnCapture
onmousemove="oWriteLocation.value=event.x + event.y";
onlosecapture="alert(event.srcElement.id +
' lost mouse capture.')"
>

<P>Mouse capture has been set to this gray division (DIV)
at load time using the setCapture method. The text area will
track the mousemove event through the <B>x</B>
and <B>y</B> properties of the event object.<BR>
<P>Event bubbling works as usual on objects within a
container that has mouse capture. Demonstrate this concept by
clicking the button below or changing the active window from
this one, and then back. After oOwnCapture loses mouse capture,
the text area continues tracking the mousemove events only
while the cursor is over objects it contains.</P>
<BR><BR>
<TEXTAREA ID=oWriteLocation COLS=2>
mouse location</TEXTAREA>
</DIV>
<HR>
<DIV ID=oNoCapture>
<P><a href="http://www.never-online.net">www.never-online.net, by JK, Rank, never-online</P>
<INPUT VALUE="Move mouse over this object.">
<INPUT TYPE=button VALUE="Click to End Mouse Capture">
</DIV>
</BODY>


再者非IE浏览器(如Firefox)如何是好,FF也有window.captureEvents(Event.MOUSEDOWN)这种类似的方法,不过和IE里面的效果差别有点大(不知道是不是我用错了的原因,总之,感觉IE底下的capture比FF好得多). 在写这篇文章之前,调试代码调试了很久...JK的代码思路和我的一模一样,但是我的在Firefox里跑的时候总是丢掉mouseup事件,经过我不断的精简代码,发现....
1.在FF里用禁止事件的默认返回值,(event.preventDefault())会使onmouseup事件丢失...
2.在我原来的代码里,我为了在拖动时不给用户选择,在IE里用了onselectstart,实际上用了setCapture时,也就是禁止了在拖动时选择...,在FF里我用了一个CSS:-moz-user-select:none;,也就是这个CSS,导致在firefox里onmouseup丢失.动态的加载style即MozUserSelect为none是无效的.

还有就是在拖动时,假如capture了某个对象,在拖动时可以无限制的拖动,显示这是不合理的,习惯的范围应该是 鼠标>=0 && 鼠标<=可视文档范围,即
+展开
-HTML
<!!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
>

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Rank's HTML document</title>
 <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
 <meta http-equiv="Pragma" content="no-cache" /> 
 <meta http-equiv="Cache-Control" content="no-cache" /> 
 <meta http-equiv="Expires" content="0" />
 <meta http-equiv="ImageToolbar" content="no" />
 <style type="text/css" title="default" media="screen">
 /*<![CDATA[*/
 
 /*]]>*/
 
</style> 
 </head>
 <body>
 <div id="demo">鼠标按下,并拖动,可看到参数 <a href="http://www.never-online.net">never-online, rank</a></div>
 <script type="text/javascript">
 //<![CDATA[
 function getDocRect (wnd) {
 wnd = wnd || window;
 doc = wnd.document;
 return { 
 left :doc.documentElement.scrollLeft||doc.body.scrollLeft||wnd.pageXOffset||wnd.scrollX||0,
 top :doc.documentElement.scrollTop||doc.body.scrollTop||wnd.pageYOffset||wnd.scrollY||0,
 width :doc.documentElement.clientWidth||doc.body.clientWidth||wnd.innerWidth||doc.width||0,
 height :doc.documentElement.clientHeight||doc.body.clientHeight||wnd.innerHeight||doc.height||0 
 }
 };
 var c = 0; var b = false;
 var d = document.getElementById("demo");
 document.onmousedown = function () {
 d.innerHTML = 'down'; b = true;
 if (d.setCapture) d.setCapture();

 document.onmousemove = function (evt) {
 if (!b) return;
 evt = evt || window.event; 
 var x, y;
 var rect = getDocRect();
 x = evt.clientX;
 y = evt.clientY;
 window.defaultStatus = 'x:' +x+ ',y:' +y+ ' -- w:' +rect.width+ ',h:' +rect.height+ ',l:' +rect.left+ ',t:' +rect.top;
 if (x>=rect.width || y>=rect.height
 || 0>=x || 0>=y) {
 return;
 } 
 d.innerHTML = c++;
 };
 document.onmouseup = function () {
 if (!b) return;
 if (document.releaseCapture) document.releaseCapture();
 document.onmousemove = null;
 d.innerHTML = 'up';
 b = false;
 }
 }
 //]]>
 </script> 
 </body>
</html>

还有一个细节
就是Firefox里当你要拖动的容器里的内容(如文本)已经选中时,点击选中区域拖动鼠标(鼠标的icon在FF里显示时非法操作),再进行拖动,mouseup也会丢失,解决此问题的方法有几种:
1.拖动时始终将focus指向另外一个element,可使拖动的容器不选中.
2.拖动结束后将focus指向另外一个element
3.取消range中所有选择.

第二个解决方法比较好,而且只用focus一次.

再有一个细节
因为event(event.clientX和event.clientY)和position(即元素坐标)不属于一个坐标系. 因此当出现滚动条时要在编程时手动调整过来, 思路是计算scrollNumber的偏移值,在移动的时候加到滚动的偏移值.但是对这个仍然不是一个满意的答案,因为在某些情况下,显得拖动的滚动条出现(不出现)很不自然....

完整代码
+展开
-HTML
<!!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
>

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title> drag demo change size or change layout - http://www.never-online.net </title>
 <meta http-equiv="ImageToolbar" content="no" />
 <meta name="author" content="never-online, BlueDestiny"/>
 <meta name="keywords" content="drag, custom layout, never modules, Mozilla CSS, C#, .net, Reference, BlueDestiny, never-online"/>
 <meta name="description" content="drag demo change size or change layout, javascript reference, c sharp artilces"/>
 <meta name="creator.name" content="never-online, BlueDestiny" />
 <style type="text/css" media="all" title="Default">
 @import "/code/js/dragdemo/main.css";
 div#demoDrag { cursor:move; position:absolute; border:1px double #000; background-color:buttonface; width:200px; height:100px; color:#CC0000; text-align:center;}
 
</style> 
 </head>

 <body id="www.never-online.net">
 <div id="header">
 <h1> drag demo change size or change layout </h1>
 by JK, never-online, <a href="http://www.never-online.net">www.never-online.net</a>
 <hr>
 </div>
 <div class="wrapper">
 <div class="content">
 <input type="radio" value="size" name="custom" id="size"/>
 <label for="size">拖动改变尺寸</label>
 <input type="radio" value="layout" name="custom" id="layout" checked/>
 <label for="layout">拖动改变布局</label>
 </div>
 </div>
 <div id="demoDrag" onmousedown="handlestart(event, this)">Easy to write the drag code, <br>this is a simple demo, <br>by never-online,<br> http://www.never-online.net</div>
 <script type="text/javascript">
 //<![CDATA[

 function getAbsolutePosition (e) {

 var width = e.offsetWidth;
 var height = e.offsetHeight;
 var left = e.offsetLeft;
 var top = e.offsetTop;
 while (e=e.offsetParent) {
 left += e.offsetLeft;
 top += e.offsetTop;
 };
 var right = left+width;
 var bottom = top+height;
 return {
 'width': width, 'height': height,
 'left': left, 'top': top,
 'right': right, 'bottom': bottom
 }
 };

 function getDocRect (wnd) {
 wnd = wnd || window;
 doc = wnd.document;
 return { 
 left :doc.documentElement.scrollLeft||doc.body.scrollLeft||wnd.pageXOffset||wnd.scrollX||0+10,
 top :doc.documentElement.scrollTop||doc.body.scrollTop||wnd.pageYOffset||wnd.scrollY||0,
 width :doc.documentElement.clientWidth||doc.body.clientWidth||wnd.innerWidth||doc.width||0,
 height :doc.documentElement.clientHeight||doc.body.clientHeight||wnd.innerHeight||doc.height||0 
 }
 };

 var elDrag = null
 var bLayout = true
 var isIE = !!window.ActiveXObject;
 var bDraging = false;

 var handlestart = function (evt, el) {
 var rect = getDocRect();
 var p = getAbsolutePosition(el);
 bLayout = document.getElementById('layout').checked?true:false;
 bDraging = true;
 evt = window.event||evt; 
 elDrag = el; 
 if (elDrag.setCapture) elDrag.setCapture();
 elDrag.onlosecapture = function() { handlestop(); }
 elDrag.deltaX = evt.clientX+rect.left-(bLayout?p.left:p.width);
 elDrag.deltaY = evt.clientY+rect.top-(bLayout?p.top:p.height);
 };
 
 var handledraging = function (evt) {
 if (!bDraging) return false
 evt = window.event||evt;
 try {
 var rect = getDocRect();
 var x, y;
 x = evt.clientX+rect.left-elDrag.deltaX;
 y = evt.clientY+rect.top-elDrag.deltaY;
 if (bLayout) {
 if (evt.clientX>1 && evt.clientX<=rect.width)
 elDrag.style.left = x +'px';
 if (evt.clientY>1 && evt.clientY<=rect.height)
 elDrag.style.top = y +'px'
 } else {
 if (x>1 && evt.clientX<=rect.width) 
 elDrag.style.width = x +'px';
 if (y>1 && evt.clientY<=rect.height) 
 elDrag.style.height = y +"px";
 }
 } catch (ex) {};
 };

 var handlestop = function (evt) {
 evt = evt || window.event;
 if (!bDraging) return false;
 if (elDrag.releaseCapture) elDrag.releaseCapture();
 document.body.focus();
 bDraging = false;
 };

 document.onmousemove = handledraging;
 document.onmouseup = handlestop;

 //]]>
 </script> 
 </body>
</html>



duoyi [ 2008-03-19 20:40:48 ]
不错,onmouseup丢失的情况确实很常见,还有另一种情况是当这个拖动的div中有图片或链接时,如果是点图片或链接开始拖动,拖出窗口时也会导致onmouseup丢失,以前我的做法是定一个全局变量(dragging)表示拖拽正在进行中,当开始拖拽时先判断是否有未完成的拖拽事件~
Rank [ 2008-03-20 13:47:01 ]
duoyi: 嗯...这个问题也得考虑进去, 等有空的时候再把这篇文章给完善了
JK还发现一个一定会导致IE onmouseup失效的问题,用capture也没有用的,但是这样的问题很少出现,.
在拖动中也就是句柄onmousemove的执行中,然后我按alt+tab切换到其它窗口,然后再切换回来,结果onmouseup丢失,所以我在写setCapture的时候用"而是在整个窗体区域"就是指当前IE文档的区域, 要防止用户切换就只能替换掉keycode了,但替换掉对于UE来说不太好,这个就难抉择了.
user2008 [ 2008-05-15 23:20:34 ]
IE有个onlosecapture事件的,可以解决alt+tab的问题
Rank [ 2008-05-20 03:49:10 ]
@user2008
没错的,可以用这个事件来解决.:)
muxrwc [ 2008-06-30 10:32:51 ]
BL大哥。。。
window.captureEvents(Event.MOUSEDOWN)

貌似FF不用加这个就可以了。。
FF是默认鼠标捕获的,这个貌似JK大哥说过。。。
然后。某次JK还给出了释放selection的代码。即
IE
document.selection.empty(); //虽然IE鼠标捕获即capture
FF
window.getSelection().removeAllRanges(); //这个不会导致FF的鼠标捕获丢失^^

不过。。
alt + tab的丢失方案确实挺BT。。。
虽然在IE里把鼠标捕获放到move里可以解决。不过FF貌似没有考虑到好的方案。。。

某天,随手写了个Drag。。。

http://www.cntuw.com/topic_203084__2.html
地址是这个。。。
PS:这论坛广告恶劣点。。。
Rank [ 2008-07-01 13:34:45 ]
代码很不错的写法,
this.o.setCapture && this.o.releaseCapture();
你说的JK说的移除selection的东西,可能他自己也不知道了。因为这个拖动也和他商量过的。他现在老忘记自己说过的话。。。
alt+tab,IE里可以有onloseCapture事件来解决,但总之IE下用capture要很小心,我上次就遇到过。capture如果成了一个循环(即不停的去capture而没有得到释放)会导致窗口“死掉”。因此很多框架都采用通用的方案。都是用event.button来做,因为所有浏览器都通用。(在move的时候判断event.button),不用再处理特例。在没有BT问题的时候是不错的选择。
[最后修改由 Rank, 于 2008-07-01 13:36:53]
muxrwc [ 2008-07-01 17:28:53 ]
引用
FlashSoft 2007-08-20 15:34:48
经验共享:
取消选择区域的方法
FF
window.getSelection().removeAllRanges();
IE
document.selection.empty();

用途:
拖拽组件的时候,取消一些用户误操作的选中的文字
哈哈。。。搜索了下聊天记录。。。
如果成了一个循环(即不停的去capture而没有得到释放)会导致窗口“死掉”。
这个到是没注意过。。。
因为一般只把鼠标捕获放到down中。。。
貌似清除selection也应该放到down中。。。(虽然一直都是写在move里)
Rank [ 2008-07-01 20:54:42 ]
这句代码看上去的确是简洁:
this.o.setCapture && this.o.releaseCapture();
但我回头一想,如果JS版本升级是否支持这种写法,需要商榷。所以还是建议用原始的if好了。
PS。你把JS和flashsoft搞混了吧~
也许我可以做一个capture的示例,虽然这有可能是developer写代码造成的假死,但为了健壮,我感觉还是小心为妙。
还有用capture有一点也要注意的,假如一个页面很长,我在拖动布局的时候,capture了,那么他就不会自动滚屏~,因此还得把自动滚屏给写进去。
[最后修改由 Rank, 于 2008-07-01 21:59:42]
muxrwc [ 2008-07-02 10:35:57 ]
a && b
这个应该没问题吧。。。ECMA4不支持这种隐转了???
如果不支持的话,用if应该也是一样的道理吧。。。
&&操作的语义应该不会改吧。。。肯定是左边成立才会执行右边啊
引用
你把JK和flashsoft搞混了吧~
记错了。。下次不会忘记了。
capture不会自动滚屏,这个好麻烦。。。
Rank [ 2008-07-02 12:01:45 ]
嗯,&&不会有问题的。。。是偶错了。。。因为这是基本“与”操作。
但是使用&&和||要注意一下:
记得上次我还问吴亮,JK
alert(false&&1||0);
这个能不能相当于?:三元符。
后来吴亮说用卡诺图可以化简得到答案。有一个结果与?:不同。
muxrwc [ 2008-07-02 12:48:22 ]
嗯。。。貌似唯一不同是
1&&false||0;

1 ? false : 0;

那个卡诺图没听说过。。。
muxrwc [ 2008-07-02 15:15:11 ]
貌似window.onblur可以检测到alt + tab

这样就避免了louse的使用。。。
偶想重新写个DRAG。。。通过e来获取moveObj
Rank [ 2008-07-02 15:26:04 ]
window.blur?
先看这个。。。IE与其它浏览器不同,firefox还有个bug,参见:
http://www.never-online.net/blog/article.asp?id=218

如果是capture的话,就可以用object.onlosecapture事件
[最后修改由 Rank, 于 2008-07-02 15:26:41]
muxrwc [ 2008-07-02 16:43:56 ]
刚刚测试了下。。。
貌似ie的window blur和 FF,OP的window blur还有不同。。。
IE是返回页面时才检测到。。。我真寒。。。
那IE还是用louse好了。。
不过如果是drag的话在FF里iframe出现也会导致丢失鼠标捕获的,貌似只能把IFRAME用XX盖住。。。

:D,貌似我测试FF里的blur没发生DOWN的问题。。。(你发的DEOM有发生)
我在blur里写把blur干掉^^

补充下。。。我本来想拿event.target来抓moveObj,结果发现完全不行。。。
[最后修改由 muxrwc, 于 2008-07-02 16:46:45]
muxrwc [ 2008-07-02 18:11:42 ]
http://topic.csdn.net/u/20080702/17/a9c334f0-e983-4981-9dc2-b4cea201e0d1.html

重新写了下那个。。。

加支付宝好友偷能量挖...


评论(0)网络
阅读(173)喜欢(0)JavaScript/Ajax开发技巧