JavaScript 拖放效果

拖放效果,也叫拖拽、拖动,学名Drag-and-drop ,是最常见的js特效之一。
如果忽略很多细节,实现起来很简单,但往往细节才是难点所在。
这个程序的原型是在做图片切割效果的时候做出来的,那时参考了好几个同类的效果,跟muxrwcBlueDestiny学习了不少东西。
虽然每次整理都觉得很好了,不过每隔一段时间又会发现得某个地方可以改善,某个地方有错误,某些需求需要实现,就像自己学习的知识那样。

这里考虑到有的人可能只需要简单的拖放,所以有一个简化版的拖放SimpleDrag,方便学习。

效果预览

ps:在maxthon下如果开启广告过滤的话很可能会被过滤掉(不知有什么方法可以避免)。


拖放状态:未开始


程序说明

【程序原理】

这里以SimpleDrag为例说一下基本原理。

首先初始化程序中要一个拖放对象:

this.Drag = $(drag);


还要两个参数在开始时记录鼠标相对拖放对象的坐标:

this._x = this._y = 0;


还有两个事件对象函数用于添加移除事件:

this._fM = BindAsEventListener(thisthis.Move);
this._fS = Bind(thisthis.Stop);


分别是拖动程序和停止拖动程序。
拖放对象的position必须是absolute绝对定位:

this.Drag.style.position = "absolute";


最后把Start开始拖放程序绑定到拖放对象mousedown事件:

addEventHandler(this.Drag, "mousedown", BindAsEventListener(thisthis.Start));


鼠标在拖放对象按住,就会触发Start程序,主要是用来准备拖动,在这里记录鼠标相对拖放对象的坐标:

this._x = oEvent.clientX - this.Drag.offsetLeft;
this._y = oEvent.clientY - this.Drag.offsetTop;


并把_fM拖动程序和_fS停止拖动程序分别绑定到document的mousemove和mouseup事件:

addEventHandler(document, "mousemove"this._fM);
addEventHandler(document, 
"mouseup"this._fS);


注意要绑定到document才可以保证事件在整个窗口文档中都有效,如果只绑定到拖放对象就很容易出现拖太快就脱节的现象。

当鼠标在文档上移动时,就会触发Move程序了,这里就是实现拖动的程序。
通过现在鼠标的坐标值跟开始拖动时鼠标相对的坐标值的差就可以得到拖放对象应该设置的left和top了:

this.Drag.style.left = oEvent.clientX - this._x + "px";
this.Drag.style.top = oEvent.clientY - this._y + "px";


最后放开鼠标后就触发Stop程序结束拖放。
这里的主要作用是把Start程序中给document添加的事件移除:

removeEventHandler(document, "mousemove"this._fM);
removeEventHandler(document, 
"mouseup"this._fS);


这样一个简单的拖放程序就做好了,下面说说其他扩展和细节部分。

【拖放锁定】

锁定分三种,分别是:水平方向锁定(LockX)、垂直方向锁定(LockY)、完全锁定(Lock)。
这个比较简单,水平和垂直方向的锁定只要在Move判断是否锁定再设置left和top就行,如果是完全锁定就直接返回。

if(!this.LockX){ this.Drag.style.left = ; }
if(!this.LockY){ this.Drag.style.top = ; }


【触发对象】

触发对象是用来触发拖放程序的,程序中通过Handle属性设置。有的时候不需要整个拖放对象都用来触发,这时就需要触发对象了。
使用了触发对象后,进行移动的还是拖放对象,只是用触发对象来触发拖放(一般的使用是把触发对象放到拖放对象里面)。
ps:触发对象的另一个用法是通过设置相同的Handle,实现一个触发对象同时拖放多个拖放对象。

【范围限制】

要设置范围限制必须先把Limit设为true。范围限制分两种,分别是固定范围和容器范围限制,主要在Move程序中设置。
原理是当比较的值超过范围时,修正left和top要设置的值使拖放对象能保持在设置的范围内。

【固定范围限制】

容器范围限制就是指定上下左右的拖放范围。
各个属性的意思是:
上(mxTop):top限制;
下(mxBottom):top+offsetHeight限制;
左(mxLeft):left限制;
右(mxRight):left+offsetWidth限制。

如果范围设置不正确,可能导致上下或左右同时超过范围的情况,程序中有一个Repair程序用来修正范围参数的。
Repair程序会在程序初始化和Start程序中执行,在Repair程序中修正mxRight和mxBottom:

this.mxRight = Math.max(this.mxRight, this.mxLeft + this.Drag.offsetWidth);
this.mxBottom = Math.max(this.mxBottom, this.mxTop + this.Drag.offsetHeight);


其中mxLeft+offsetWidth和mxTop+offsetHeight分别是mxRight和mxBottom的最小范围值。

根据范围参数修正移动参数:

iLeft = Math.max(Math.min(iLeft, mxRight - this.Drag.offsetWidth), mxLeft);
iTop 
= Math.max(Math.min(iTop, mxBottom - this.Drag.offsetHeight), mxTop);


对于左边上边要取更大的值,对于右边下面就要取更小的值。

【容器范围限制】

容器范围限制的意思就是把范围限制在一个容器_mxContainer内。
要注意的是拖放对象必须包含在_mxContainer中,因为程序中是使用相对定位来设置容器范围限制的(如果是在容器外就要用绝对定位,这样处理就比较麻烦了),还有就是容器空间要比拖放对象大,这个就不用说明了吧。
原理跟固定范围限制差不多,只是范围参数是根据容器的属性的设置的。

当设置了容器,在Repair程序如果容器的position不是relative或absolute,会自动把position设为relative来相对定位:

!this._mxContainer || CurrentStyle(this._mxContainer).position == "relative" || CurrentStyle(this._mxContainer).position == "absolute" || (this._mxContainer.style.position = "relative");


ps:其中CurrentStyle是用来获取最终样式(详细看这里的最终样式部分)。

注意如果在程序执行之前设置过拖放对象的left和top而容器没有设置relative,在自动设置relative时会发生移位现象,所以程序在初始化时就执行一次Repair程序防止这种情况。因为offsetLeft和offsetTop要在设置relative之前获取才能正确获取值,所以在Start程序中Repair要在设置_x和_y之前执行。

由于是相对定位,对于容器范围来说范围参数上下左右的值分别是0、clientHeight、0、clientWidth。
clientWidth和clientHeight是容器可视部分的宽度和高度(详细参考这里)。
为了容器范围能兼容固定范围的参数,程序中会获取容器范围和固定范围中范围更小的值:

Code


因为设置相对定位的关系,容器_mxContainer设置过后一般不要取消或修改,否则很容易造成移位异常。

【鼠标捕获】

我在一个拖放实例中看到,即使鼠标移动到浏览器外面,拖放程序依然能够执行,仔细查看后发现是用了setCapture。
鼠标捕获(setCapture)是这个程序的重点,作用是将鼠标事件捕获到当前文档的指定的对象。这个对象会为当前应用程序或整个系统接收所有鼠标事件。
使用很简单:

this._Handle.setCapture();


setCapture捕获以下鼠标事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout。
程序中主要是要捕获onmousemove和onmouseup事件。
msdn的介绍中还说到setCapture有一个bool参数,用来设置在容器内的鼠标事件是否都被容器捕获。
容器就是指调用setCapture的对象,大概意思就是:
参数为true时(默认)容器会捕获容器内所有对象的鼠标事件,即容器内的对象不会触发鼠标事件(跟容器外的对象一样);
参数为false时容器不会捕获容器内对象的鼠标事件,即容器内的对象可以正常地触发事件和取消冒泡。
而对于容器外的鼠标事件无论参数是什么都会被捕获,可以用下面这个简单的例子测试一下(ie):

Code


这里的参数是true,一开始body会捕获所有鼠标事件,即使鼠标经过div也不会触发onmousemove事件。
换成false的话,div就可以捕获鼠标事件,就能触发div的onmousemove事件了。

拖放结束后还要使用releaseCapture释放鼠标,这个可以放在Stop程序中:

this._Handle.releaseCapture();


setCapture是ie的方法,对于dom也有对应的captureEvents和releaseEvents方法,但这两个方法现在的ff已经不支持了(如果还想了解一下的话可以看w3c中dom2的Event Capture部分)。

那是不是没有鼠标捕获就不能在浏览器外执行拖动呢?并不是这样,大家测试SimpleDrag的话可以发现没有设置鼠标捕获也能在浏览器外拖动(ie和ff都可以)。
那时因为程序把mousemove事件绑定到document了,如果换成this.Drag就没有了这个效果了。
不过细心的话也能发现这个跟用setCapture设置的鼠标捕获还是有很大不同的。
看来这个效果是专门为document而设的,我想这是为了能在浏览器外也能对文档进行一些特殊操作,例如拖动鼠标选择一段文本,即使拖到浏览器外也能继续选择。

这里说说ie中有个奇怪的现象,如果在SimpleDrag的Move程序中加入document.selection.empty()来清除选择(下面会介绍),那么捕获也会失效。
这个我也想不明白,希望高人能指点。

那是不是就不需要用setCapture鼠标捕获了呢?也不是,setCapture在下面说明的焦点丢失、取消默认动作上都很有用处的,而对于ff还是需要把事件绑定到document来捕获浏览器外的拖动事件。

注意ff2下的鼠标捕获有一个bug,当拖放对象内部没有文本内容并拖放到浏览器外时捕获就会失效。
给拖放对象插入一个空文本,例如<font size='1px'>&nbsp;</font>就可以解决,不过这个bug在ff3已经修正了。

【焦点丢失】

一般情况下,鼠标捕获都能正常捕获事件,但如果浏览器窗口的焦点丢失就会导致捕获失效。
我暂时测试到会导致焦点丢失的操作包括切换窗口(包括alt+tab),alert和popup等弹出窗体。
当焦点丢失时应该同时执行Stop程序结束拖放,但当焦点丢失就不能捕获mouseup事件也就是不能触发_fS。
还好ie有onlosecapture事件会在捕获失效时触发,针对这个情况可以这样设置:

addEventHandler(this._Handle, "losecapture"this._fS);


并在Stop程序中移除:

removeEventHandler(this._Handle, "losecapture"this._fS);

但ff没有类似的方法,不过muxrwc找到一个替代losecapture的window.onblur事件,那么可以在Start程序中设置:

addEventHandler(window, "blur"this._fS);


在Stop程序中移除:

removeEventHandler(window, "blur"this._fS);

那ie也有window.onblur事件,用window.onblur代替losecapture不就可以省一段代码了吗。
接着我做了一些测试,发现基本上触发losecapture的情况都会同时触发window.onblur,看来真的可以。
于是我修改程序用window.onblur代替losecapture,但测试后就出问题了,我发现如果我用alt+tab切换到另一个窗口,拖动还可以继续,但这个时候应该是已经丢失焦点了。

于是我逐一排除测试和程序代码,结果发现如果使用了DTD,那么window.onblur会在再次获得焦点时才会触发。
大家可以用下面这段代码测试:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script>window.onblur=function(){alert(1)}</script>


在切换到其他程序后,再切换回来才会触发window.onblur,还有几个比较怪异的状况就不说了,反正ie用window.onblur是不理想的了。

【取消默认动作】

对选择状态的文本内容、连接和图片等进行拖放操作会触发系统的默认动作,例如ie中拖动图片鼠标会变成禁止操作状态,这样会导致这个拖放程序执行失败。

不过ie在设置了setCapture之后,通过用户界面用鼠标进行拖放操作和内容选择都会被禁止。
意思就是setCapture之后就不能对文档内容进行拖放和选择,注意这里的拖放是指系统的默认动作,例如ondragstart就不会被触发。
不过如果setCapture的参数是false的话,容器内的对象还是可以触发事件的(具体看鼠标捕获部分),所以setCapture的参数要设成true或保留默认值。

而ff的鼠标捕获没有这个功能,但可以用preventDefault来取消事件的默认动作来解决:

oEvent.preventDefault();


ps:据说使用preventDefault会出现mouseup丢失的情况,但我在ff3中测试没有发现,如果各位发现任何mouseup丢失的情况,务必告诉我啊。

【清除选择】

ie在设置setCapture之后内容选择都会被禁止,但也因此不会清除在设置之前就已经选择的内容,而且设置之后也能通过其他方式选择内容,
例如用ctrl+a来选择内容。
ps:onkeydown、onkeyup和onkeypress事件不会受到鼠标捕获影响。
而ff在mousedown时就能清除原来选择的内容,但拖动鼠标,ctrl+a时还是会继续选择内容。
不过在取消了系统默认动作之后,这样的选择并不会对拖放操作造成影响,这里设置主要还是为了更好的体验。

以前我用禁止拖放对象被选择的方法来达到目的,即ie中设置拖放对象的onselectstart返回false,在ff中设置样式MozUserSelect(css:-moz-user-select)为none。
但这种方法只能禁止拖放对象本身被选择,后来找到个更好的方法清除选择,不但不影响拖放对象的选择效果,还能对整个文档进行清除:
ie:document.selection.empty()
ff:window.getSelection().removeAllRanges()
为了防止在拖放过程中选择内容,所以把它放到Move程序中,下面是兼容的写法:

window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();


这里说说ff下有一个比较奇怪的现象,在SimpleDrag中第一次拖放没有问题,但第二次拖放时鼠标就会显示禁止操作的样式(拖动失败)。
我发现如果在Start程序中阻止默认动作(preventDefault)或清除选择(window.getSelection().removeAllRanges())就能正常了。
所以我估计是在点击事件后,拖放对象已经默认设置成选择状态了(虽然看起来不是),再拖动时就触发了系统的默认拖动事件。
为了支持推断,我在html插入一段文本,然后在每一次拖放之后选择一下那段文本(间接取消拖放对象的选择),那就能正常了。
不过暂时还没找到官方说明,所以还不能下结论。

【margin】

还有一个情况,当拖放对象设置了margin,那么拖放的时候就会错位(给SimpleDrag的拖放对象设置margin就可以测试)。
原因是在Start程序设置_x和_y时是使用offset获取的,而这个值是包括margin的,所以在设置left和top之前要减去这个margin。
但如果在Start程序中就去掉margin那么在Move程序中设置范围限制时就会计算错误,
所以最好是在Start程序中获取值:

this._marginLeft = parseInt(CurrentStyle(this.Drag).marginLeft) || 0;
this._marginTop = parseInt(CurrentStyle(this.Drag).marginTop) || 0;


在Move程序中设置值:

this.Drag.style.left = iLeft - this._marginLeft + "px";
this.Drag.style.top = iTop - this._marginTop + "px";


要注意margin要在范围修正之后再设置,否则会错位。

【透明背景bug】

在ie有一个透明背景bug(不知算不算bug),可以用下面的代码测试:

Code


点击div的背景会触发不了事件(点击边框或div里面的对象是可以触发的)。
到底什么时候会出现这个bug呢,再用下面的代码测试:

Code


测试代码中我把背景颜色(包括body)设成灰色,首先可以看出在蓝色div(测试对象)内只要触发点是在灰色上面,就能触发事件;相反,在其他不是背景的地方,即使是边框、图片,也不能触发事件。
就像是把灰色的背景的补到蓝色div上来,而且仅仅是背景能这样,多奇怪的设定啊。
这里要说明的是body比较特别,不管背景是不是透明,只要触发点是直接在body上就能触发事件。
我的结论是如果触发事件的对象背景是透明的,而事件的触发点不在对象内的元素上,也不是直接在body上,而且透明背景外没有非透明背景的话,那么事件触发就会失败。
这个结论写得不太好,因为我都不知改怎么表达这奇怪的设定,希望各位能明白。
ps:这里设置图片背景跟颜色背景效果是一样的。

那最好解决的方法就是给对象设一个非透明背景,但有时需求正好是要透明的,那怎么办呢?
首先想到的是加上背景色后设置完全透明,但这样连边框,容器内的对象等都完全透明了,不够好。
如果能保证触发点直接在body或非背景上也可以,如果这个也不能保证呢?
我想到的一个解决方法是在容器里面加一个层,覆盖整个容器,并设置背景色和完全透明:

Code


ps:不要忘了设置fontSize,否则就有一个默认最小高度。
当发现程序有这个bug出现,把程序可选参数Transparent设为true就会自动插入这样一个层了。

到这里插入一个小知识吧,细心的话会发现上面的测试代码中我给html设了一个背景色。
大家可以去掉这个背景色,会发现背景色会设置到整个页面,虽然一直都是用body设置页面背景色,但现在会不会有一个疑惑,body是红色框的部分,为什么设置它的背景色就能应用到整个页面,而给html设置了背景色就又“正常”显示了呢。
这个可以从CSS21的w3c标准中关于background的部分看到原因:
For HTML documents, however, we recommend that authors specify the background for the BODY element rather than the HTML element. For HTML documents whose root HTML element has computed values of 'transparent' for 'background-color' and 'none' for 'background-image', user agents must instead use the computed value of those properties from that HTML element's first BODY element child when painting backgrounds for the canvas, and must not paint a background for that BODY element. Such backgrounds must also be anchored at the same point as they would be if they were painted only for the root element. This does not apply to XHTML documents.

我英文很烂,就勉强翻译一下吧:
对于HTML文档,我们建议作者(这是对浏览器的制作者说的)使用BODY元素的background,而不是HTML元素的。如果HTML文档的根元素(HTML元素)的'background-color'是'transparent',同时'background-image'是'none'(这两个刚好就是默认值),那么在设置背景时就取HTML元素的第一个BODY子元素的属性值,并且不再渲染那个元素的background。
后面两个句就看不太懂了,不过已经足够解析原因了。

【iframe】

如果页面上有嵌入iframe,那就要注意了,因为鼠标捕获在iframe上会有问题。

例如在拖放容器内的一个iframe上快速移动拖放,或者鼠标拖动到容器外的一个iframe上,反正就是鼠标在iframe上(注意没有其他元素隔开),就会出问题:
首先是捕获的失效,鼠标在iframe上,就拖不动了,但并不会触发losecapture,更不用说window的blur了,这个在ie和ff都是一样;
其次ie里在iframe多摩擦几次,还可能导致ie死掉(原因不明)。

下面是我想到的几种方法:
隐藏页面的iframe,比较简单,但可能有些在iframe中重要的信息也被隐藏,或者造成页面布局的错位,用户体验不好;
鼠标移动到iframe后取消拖放,难度不大,但同样用户体验不好;
每个iframe用一个透明的层遮住,很麻烦,要计算好每个iframe的位置和大小;
用一个透明的层把整个页面遮住,比较推荐,也比较简单,下面介绍这种做法。

我在仿LightBox内容显示效果做的那个覆盖层正好能应用在这里,首先实例化一个透明的覆盖层:

var ol = new OverLay({ Opacity: 0 });


然后在onStart和onStop事件中添加ol.Show()和ol.Close()来显示和隐藏覆盖层就可以了,这样只要不是在iframe触发拖放就没有问题了。

有其他更好的方法也请各位指教。

暂时就研究到这里,想不到小小的拖放就有这么多的学问。
还有滚屏等这些都还没考虑到呢,等以后有需要了再来研究拉。


使用说明

实例化时只需要一个参数,就是拖放对象:

var drag = new Drag("idDrag")


有以下这些可选参数和属性:
属性:默认值//说明
Handle:"",//设置触发对象(不设置则使用拖放对象)
Limit:false,//是否设置范围限制(为true时下面参数有用,可以是负数)
mxLeft:0,//左边限制
mxRight:9999,//右边限制
mxTop:0,//上边限制
mxBottom:9999,//下边限制
mxContainer:"",//指定限制在容器内
LockX:false,//是否锁定水平方向拖放
LockY:false,//是否锁定垂直方向拖放
Lock:false,//是否锁定
Transparent: false,//是否透明
onStart:function(){},//开始移动时执行
onMove:function(){},//移动时执行
onStop:function(){}//结束移动时执行

还有属性Drag是拖放对象,Transparent、Handle和mxContainer初始化后就不能再设置。


程序代码 

Code



完整实例下载

转载请注明出处:http://www.cnblogs.com/cloudgamer/

如有任何建议或疑问,欢迎留言讨论。

如果觉得文章不错的话,欢迎点一下右下角的推荐。

程序中包含的js工具库CJL.0.1.min.js,原文在这里

posted @ 2008-11-17 08:48 cloudgamer 阅读(63665) 评论(136) 编辑 收藏

评论共2页: 上一页 1 2 
 回复 引用   
#37楼 2008-12-09 11:24 魔兽泡泡[未注册用户]
下载的例子里面,全功能的那个很好 ,但是简单的版本的那个例子,在firefox下只能拖一下,第二次开始拖就有停顿了,老大你能改一下吗,我不擅长ui、js这些东西。。痛苦
 回复 引用 查看   
#38楼[楼主] 2008-12-10 20:07 cloudgamer      
@魔兽泡泡
我想你是用SimpleDrag
你在Move里面加上
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();

 回复 引用   
#39楼 2008-12-15 17:30 mousse[未注册用户]
讲得很细致,虽然还有些地方不太明白,但那是我菜的原因,支持下!呵呵
 回复 引用 查看   
#40楼[楼主] 2008-12-15 23:16 cloudgamer      
@mousse
有什么不明白的可以提出一起讨论

 回复 引用   
#41楼 2008-12-27 15:42 慢慢慢[未注册用户]
真的不错,支持楼主,代码漂亮,分析的也不错。
 回复 引用 查看   
#42楼 2008-12-30 16:26 Fengdesudu      
非常不错,谢谢分享
 回复 引用   
#43楼 2009-01-04 13:41 饿[未注册用户]
楼主多发些damo,很实用啊
 回复 引用   
#44楼 2009-01-05 15:36 ohYe[未注册用户]
太好了。 学习……
 回复 引用 查看   
#45楼[楼主] 2009-01-09 13:52 cloudgamer      
@慢慢慢
@Fengdesudu
@饿
@ohYe
谢谢支持

 回复 引用   
#46楼 2009-01-16 15:07 jsstarting[未注册用户]
对原理写的很详细,对我这种初学者很有用,谢谢了
 回复 引用   
#47楼 2009-01-16 23:39 fengshen[未注册用户]
最近在拜读你的作品,关于本篇有个疑问
//修正移动参数
iLeft = Math.max(Math.min(iLeft, mxRight - this.Drag.offsetWidth), mxLeft);
iTop = Math.max(Math.min(iTop, mxBottom - this.Drag.offsetHeight), mxTop);
能不能烦请详细解释下,谢谢。

 回复 引用 查看   
#48楼[楼主] 2009-01-17 00:16 cloudgamer      
@jsstarting
谢谢支持

@fengshen
就是把值限制在mxLeft和mxRight - this.Drag.offsetWidth之间

 回复 引用   
#49楼 2009-04-15 09:24 haihaizididie[未注册用户]
好强,学习了。。。
 回复 引用   
#50楼 2009-04-25 11:58 煦日流风
看过你发的好几篇文章,写的很好,受益匪浅。结合你的“仿LightBox内容显示效果”,我想让那个高亮层支持拖动,创建Drag对象时,如果Limit: true,就立马报错,下面语句报“缺少对象”
//修正错误范围参数
this.mxRight = Math.max(this.mxRight, this.mxLeft + this.Drag.offsetWidth);
如果Limit: false,则拖动时报错,下面语句报“缺少对象”
//记录鼠标相对拖放对象的位置
this._x = oEvent.clientX - this.Drag.offsetLeft;

请问,这怎么解决?

 回复 引用 查看   
#51楼[楼主] 2009-04-25 13:56 cloudgamer      
@haihaizididie
谢谢支持

@煦日流风
最好把你的例子发到我邮箱看看

 回复 引用   
#52楼 2009-04-27 13:05 煦日流风
楼主回帖真快,周末也守在线上,敬佩!

你的js源码我没动,就是在创建LightBox对象后接着创建Drag对象:
var box = new LightBox("idBox");
$("close1").onclick = function() { box.Close(); }
$("Button2").onclick = function() { box.Show(); }
new Drag("idBox");
另外一种就是在LightBox class中创建Drag对象,问题一样,换了几个参数仍然不行:
new Drag(this.Box, { mxContainer: OverLay, Handle: "idHandle", Limit: true });

下面我把我的idBox层代码贴出来,层中有个iframe :
<div id="idBox" class="lightbox" style="position:absolute;overflow:hidden;z-index:10000;width:418px; height:368px; background-color:#f7f8fd;">
<table border="0" cellpadding="0" cellspacing="0" width="422">
<tr bgcolor="#8cb2fd" id="idHandle" style="cursor:move;">
<td height="32" width="358" ><img src="Images/page/uphead.gif" width="95" height="32" /></td>
<td width="60" align="center" valign="bottom"><h3 class="outClose" onmouseover="this.className='onClose';" onmouseout="this.className='outClose';" id="close1"/>&nbsp;</td>
</tr>
<tr >
<td colspan="2" style="position:relative;" id="state"><iframe id="hiddenframe" scrolling="no" frameborder="0" height="336" width="418" src=""></iframe></td>
</tr>

</table>
</div>

 回复 引用 查看   
#53楼[楼主] 2009-04-27 13:55 cloudgamer      
@煦日流风
最好还是把你的代码email给我
我就不用再自己调了,忙啊

 回复 引用   
#54楼 2009-04-29 15:44 manlsea[未注册用户]
@cloudgamer
代码已经发到你的邮箱。

 回复 引用 查看   
#55楼[楼主] 2009-04-29 17:44 cloudgamer      
@manlsea
已经回复了

 回复 引用   
#56楼 2009-04-30 09:00 solomongg@126.com
请给我也传一份,先谢谢楼主了
solomongg@126.com 邮箱

 回复 引用 查看   
#57楼[楼主] 2009-04-30 10:20 cloudgamer      
@solomongg@126.com
上面已经有实例下载啦

 回复 引用   
#58楼 2009-05-05 16:43 煦日流风
@cloudgamer

已经收到,多谢楼主。

 回复 引用   
#59楼 2009-05-05 19:24 潘凯[未注册用户]
up 一个
 回复 引用   
#60楼 2009-05-08 17:46 wtcsy[未注册用户]
有个小bug
拖动物件上产生的alert 如:
<div id='container'><div id='title' onmousedown></div><div id=body></div></div>
拖动帮定的container
ie element.onlosecapture 或者 ff window.blur 无效噢

 回复 引用 查看   
#61楼[楼主] 2009-05-08 19:59 cloudgamer      
@潘凯
谢谢支持

@wtcsy
貌似可以啊
发个例子给我测试测试

 回复 引用   
#62楼 2009-05-09 14:41 wtcsy[未注册用户]
发到你邮箱去了额
 回复 引用 查看   
#63楼[楼主] 2009-05-09 15:41 cloudgamer      
@wtcsy
确实
因为alert在setCapture之前执行
所以没有触发losecapture
只能在alert的时候阻止冒泡咯

 回复 引用 查看   
#64楼 2009-05-25 23:59 Biny      
讲解得非常详细,不可多得的教程。支持你!
 回复 引用 查看   
#65楼 2009-06-01 21:42 wtcsy      
好象还有一个小bug
当要拖动物件会产生滚动条时,拖动他 滚动条消失,然后 鼠标和物件会错位...

 回复 引用 查看   
#66楼[楼主] 2009-06-01 22:20 cloudgamer      
@Biny
谢谢支持

@wtcsy
ie6?

 回复 引用 查看   
#67楼 2009-06-01 23:00 wtcsy      
ie6 ff3都会
 回复 引用 查看   
#68楼 2009-06-03 10:29 wtcsy      
如果一个页面里面要有基本的拖动效果 我用的就博主上面的拖动类
但是又要写一个Dialog的效果 这个要封装。我可以在封装的时候 直接用到这个拖动类吗? 我的意思就是可不可以不重新写拖动类?

 回复 引用 查看   
#69楼[楼主] 2009-06-03 11:10 cloudgamer      
@wtcsy
或者在Dialog的封装里面实例化一个拖动类咯
我在切割效果那里就是这样的

 回复 引用 查看   
#70楼 2009-06-09 12:38 荒城      
太谢谢你了
我昨天通宵想一个问题 刚刚在这里找到解决方法了 谢谢啊
真希望能和你成为朋友 多请教请教你

 回复 引用 查看   
#71楼[楼主] 2009-06-09 14:30 cloudgamer      
@荒城
谢谢支持
欢迎交流

 回复 引用   
#72楼 2009-06-12 08:43 淘淘v2009[未注册用户]
楼主好,当我和我同学看到您的文章后说js都能学这么好,那我们还学什么.net啊,呵呵,开玩笑。在这想请教一个可能有点“低智商”的问题,就是怎么在程序里设一个变量来获取最终的拖放层Drag相对于容器层的坐标呢(停止拖放的时候的坐标,为非过程状态时的坐标)?
 回复 引用 查看   
#73楼[楼主] 2009-06-12 09:22 cloudgamer      
@淘淘v2009
停止拖放时获取拖放层的offsetLeft/offsetTop应该就是你要的了

 回复 引用   
#74楼 2009-06-12 11:26 淘淘v2009[未注册用户]
我想在asp.net里面引用idShow的输出变量,我在下面写了这样一段代码
var x=this.Drag.offsetLeft;
var y=this.Drag.offsetTop;
document.write(x);
document.write(y);
来测试,结果说x,y是空的,不知道您是不是这个意思,还是要直接引用idShow啊?

 回复 引用 查看   
#75楼[楼主] 2009-06-12 11:30 cloudgamer      
@淘淘v2009
你这个写法很有问题啊
this是用在函数里的,document.write会重写文档
你可以在Stop里面加上alert(this.Drag.offsetLeft)看看

 回复 引用   
#76楼 2009-06-12 11:44 淘淘v2009[未注册用户]
这个可以,但是把Drag的相对坐标值,也就是idShow传入aspx页面的时候就会产生空值,我觉得是因为再拖动的时候能输出坐标和变量,但是一停止拖动,坐标值就获取不到了,这是我最想搞懂的,怎样在拖动结束时能保存坐标值并输出呢?哎,麻烦你了,javascript学的确实很烂啊
 回复 引用 查看   
#77楼[楼主] 2009-06-12 11:48 cloudgamer      
@淘淘v2009
说实话不太懂你问什么
你还是找个人帮帮你吧
坐标值不需要保存的啊,offsetLeft就是当前的坐标值了

 回复 引用   
#78楼 2009-06-12 16:52 @淘淘v2009 [未注册用户]
@cloudgamer
好吧,我说最后一点,呵呵,我还是不愿问别人,因为我觉得你已经是少有的高手了,说实话我们老师都还不如你啊。就是拖放以后如果尝试刷新的话,那么拖放状态又回到初始了,坐标在拖放以后只能显示出来是保存不了的,就是在层下面加一个按钮的话,一有动作Drag就回到初始状态,刷新的话以前的坐标值就失去了,怎样才能拖到一个坐标而让它保持状态呢?

 回复 引用 查看   
#79楼[楼主] 2009-06-13 09:10 cloudgamer      
@@淘淘v2009
你可以拖放结束后把坐标写入cookie
页面载入的时候就读这个值来设置坐标

 回复 引用   
#80楼 2009-06-13 09:51 淘淘v2009 [未注册用户]
ok,回去试试,谢谢大侠,请以后多多关照,这年头遇见个真正的高手不容易啊
 回复 引用   
#81楼 2009-07-18 15:45 乐游2009[未注册用户]
拖放有种更好的算法,可以不用考虑maring
 回复 引用 查看   
#82楼[楼主] 2009-07-18 16:44 cloudgamer      
@乐游2009
不知是什么方法呢

@淘淘v2009
谢谢支持


 回复 引用   
#83楼 2009-07-21 14:49 abeet[未注册用户]
关于在鼠标拖到iframe上就不再执行mousemove所定义的方法,那是因为这时候的事件在激发在iframe窗口内,不再属于本窗口的document,
所以我的解决办法是,在iframe里也引用drag.js,引用了drag.js的页面都会对mousemove作出响应,
判断是否有父窗口,父窗口里是否有拖拽元素,拖拽元素是否正在拖拽中,如果条件成立,就继续调用父窗口的move()。

 回复 引用 查看   
#84楼[楼主] 2009-07-21 15:05 cloudgamer      
@abeet
这样要iframe配合
而且比较麻烦
我觉得我的方法还是比较方便的

 回复 引用   
#85楼 2009-08-19 18:27 xiyunfang[未注册用户]
我最近要做一个迷你屋功能,涉及到拖放,但是物品在固定范围内拖动时,我希望它超出背景的部分隐藏起来(因为物品移动的范围比背景大),不知道你能提点一下不?有方法的话,可以发我邮箱不?万分感谢。
 回复 引用 查看   
#86楼[楼主] 2009-08-19 23:17 cloudgamer      
@xiyunfang
那就要做成相对定位了
拖动对象放到容器里面
然后overflow hidden
具体自己实现吧

 回复 引用   
#87楼 2009-09-01 09:47 yuanying[未注册用户]
很感谢你 看了你的代码才知道自己写的代码很垃圾
 回复 引用 查看   
#88楼 2009-09-16 10:16 水月无痕      
能不能把源码给我发到邮箱里面啊!
zhangyonghe.2009@163.com

 回复 引用 查看   
#89楼[楼主] 2009-09-16 10:31 cloudgamer      
@水月无痕
@yuanying
谢谢支持
上面就有源码下载了

 回复 引用 查看   
#90楼 2009-09-16 10:40 水月无痕      
好像没有啊!没有发现!
 回复 引用 查看   
#91楼[楼主] 2009-09-16 10:48 cloudgamer      
@水月无痕
不是有一个
完整实例下载
了嘛
直接贴给你了
http://files.cnblogs.com/cloudgamer/Drag.rar

 回复 引用 查看   
#92楼 2009-09-16 10:56 水月无痕      
谢谢楼主,收到了!
 回复 引用   
#93楼 2009-09-23 15:33 尜尜[未注册用户]
拜读 小菜鸟谢过
 回复 引用   
#94楼 2009-09-28 21:29 一三[未注册用户]
this._x = oEvent.clientX - this.Drag.offsetLeft;
this._y = oEvent.clientY - this.Drag.offsetTop;
这个不就是这个DIV的left和top吗?

为什么要this.Drag.style.left = oEvent.clientX - this._x + "px";
this.Drag.style.top = oEvent.clientY - this._y + "px";

 回复 引用 查看   
#95楼[楼主] 2009-09-29 08:33 cloudgamer      
@一三
x y是原来的位置
后面才是移动后的位置

 回复 引用 查看   
#96楼[楼主] 2009-09-29 08:33 cloudgamer      
@水月无痕
@尜尜
谢谢支持

 回复 引用   
#97楼 2009-09-29 15:43 一三[未注册用户]
@cloudgamer
恩,明白了。。谢谢

 回复 引用   
#98楼 2009-10-15 09:54 sos[未注册用户]
感谢博主!
 回复 引用 查看   
#99楼 2009-10-23 11:37 studyinter      
好人啊
 回复 引用 查看   
#100楼 2009-10-25 20:45 vs之bug      
很感谢博主分享,但我发现有个问题:
如果在div中加上图片或直接将Div换成图片的话,在ie下拖动会出现错误,
我不知道是什么原因,
博主如果有时间的话能不能解决一下?

例如将div的代码换成:
<div id="idDrag" style="border:5px solid #0000FF; background:#C4E3FD; width:150px; height:150px;"><img src="77.jpg" style="width:100px;height:100px"/></div>


 回复 引用 查看   
#101楼[楼主] 2009-10-25 21:39 cloudgamer      
@vs之bug
貌似没有问题啊
你不是用SimpleDrag吧

 回复 引用   
#102楼 2009-12-06 12:45 OKba[未注册用户]
这个拖动有问题,怎么拖下就不见了。。。IE6
 回复 引用 查看   
#103楼[楼主] 2009-12-06 15:24 cloudgamer      
@OKba
是不是广告过滤拦截掉了

 回复 引用 查看   
#104楼 2010-04-13 13:55 kaka100      
真不错

 回复 引用   
#105楼 2010-06-25 16:00 kering[未注册用户]
如何能在载入时把初始X,Y的数据赋给对象?也就是我想在打开页面时即让对象移动到指定的坐标,请指点,多谢
 回复 引用 查看   
#106楼[楼主] 2010-06-25 16:03 cloudgamer      
@kaka100
谢谢支持

 回复 引用 查看   
#107楼[楼主] 2010-06-25 16:03 cloudgamer      
@kering
你直接用css设置top left就行了

 回复 引用   
#108楼 2010-06-25 18:58 kering[未注册用户]
@cloudgamer
非常感谢你的回复。技术如此之高同时还如此热心的我还真的很少见过,楼主你算是我见过的第一位了!学习中

 回复 引用 查看   
#109楼[楼主] 2010-06-25 21:53 cloudgamer      
@kering
谢谢支持

 回复 引用   
#110楼 2010-08-09 11:19 Yuffiy[未注册用户]
如果每次移动只移动20PX,而不是连续移动应该如何实现呢?
 回复 引用 查看   
#111楼[楼主] 2010-08-09 14:19 cloudgamer      
@Yuffiy
Move里面你把要移动的坐标数去掉除以20的余数就行了

 回复 引用   
#112楼 2010-08-26 22:53 Web圈[未注册用户]
博主对Dom事件琢磨的很透彻,  难得有这样的耐心研究....佩服..
 回复 引用 查看   
#113楼 2010-09-02 22:28 otheno      
好!
 回复 引用 查看   
#114楼 2010-09-30 21:05 无爱      
给楼主找了几个BUG:
1.IE6 select元素优先级高,解决:加iframe挡住(简单)
2.对于没有范围限制的拖动,如果拖动到了浏览器边源,产生问题,主要是那个clientX-drag.offsetLeft这个有问题,没有加scrollLeft之类的东西
3,ie在某些环境下的html border为2px的bug,总觉得你这个clientX-drag.offsetLeft很不严谨
4.关于那个透明BUG,对于解决方案加个DIV的问题,有一个BUG,CSS设置有问题,要加position的相关属性,不然会出现drag如果内部有内容,会出现拖动范围不正确的结果
关于透明BUG,其实上内部比你讲解有还要复杂很多,有很多种奇怪的现象...

对于拖动,我也说一些自已的想法:
按下鼠标时,建立一个全屏的全透DIV,如果是IE6,则建立一个全屏的全透Iframe加一个全屏的全透div,置于托运元素下,这样可以解决很多问题,如:透明BUG,ifarmeBUG,select BUG和部分清除选择的问题

楼主的实战能力很强,感谢楼主提供如此实用的程序


 回复 引用 查看   
#115楼 2010-09-30 23:03 无爱      
var getCoords = function(el){
var box = el.getBoundingClientRect(),
doc = el.ownerDocument,
body = doc.body,
html = doc.documentElement,
clientTop = html.clientTop || body.clientTop || 0,
clientLeft = html.clientLeft || body.clientLeft || 0,
top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop ) - clientTop,
left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft;
return { 'top': top, 'left': left };
};
//给楼主一个,来自jquery

 回复 引用 查看   
#116楼[楼主] 2010-09-30 23:30 cloudgamer      
@otheno
@Web圈
谢谢支持

 回复 引用 查看   
#117楼[楼主] 2010-09-30 23:31 cloudgamer      
@无爱
谢谢
有空再研究研究

 回复 引用 查看   
#118楼 2010-10-01 02:04 无爱      
不知道为什么,总觉得程序的结构性和逻辑性都不怎么样,感觉代码乱得很,但是自已也只能写成这样,觉得很不自在!
 回复 引用 查看   
#119楼[楼主] 2010-10-01 23:41 cloudgamer      
@无爱
欢迎指点

 回复 引用 查看   
#120楼 2010-10-06 00:16 笨蛋的座右铭      
好消息,今天看另一个drag的时候,看到的:
Mozilla 的setCapture
window.captureEvents(Event.eventType)
window.releaseEvents(Event.eventType)
怕的是这是mozilla的私有属性!
希望是标准方法吧!

 回复 引用 查看   
#121楼 2010-10-06 00:20 笨蛋的座右铭      
楼主啊,我也准备写博客了,不建议我模仿你的方式吧,我的想法是在你的基础上进行改良与重构,当然我会注明原文出处,恳请你的同意
 回复 引用 查看   
#122楼[楼主] 2010-10-06 08:24 cloudgamer      
@笨蛋的座右铭
ff的捕获是自动的不用设置的

不介意

 回复 引用 查看   
#123楼 2010-10-14 15:48 fangsui      
这个是你从蓝色理想上复制的 还是那上面复制你的?
另外楼主说的 遨游浏览器会屏蔽掉
很简单 div的ID或者class不要出现“ad”这样的字眼,

 回复 引用 查看   
#124楼[楼主] 2010-10-14 16:35 cloudgamer      
@fangsui
这个是原创哦

 回复 引用 查看   
#125楼 2010-11-01 17:24 笨蛋的座右铭      
程序结构有问题(个人感觉),问题是initialize和setOptions方法感觉重复性太大
    this.SetOptions(options);
    
    this.Limit = !!this.options.Limit;
    this.mxLeft = parseInt(this.options.mxLeft);
    this.mxRight = parseInt(this.options.mxRight);
    this.mxTop = parseInt(this.options.mxTop);
    this.mxBottom = parseInt(this.options.mxBottom);
    
    this.LockX = !!this.options.LockX;
    this.LockY = !!this.options.LockY;
    this.Lock = !!this.options.Lock;
    
    this.onStart = this.options.onStart;
    this.onMove = this.options.onMove;
    this.onStop = this.options.onStop;
    
 
   
  SetOptions: function(options) {
    this.options = {//默认值
        Handle:            "",//设置触发对象(不设置则使用拖放对象)
        Limit:            false,//是否设置范围限制(为true时下面参数有用,可以是负数)
        mxLeft:            0,//左边限制
        mxRight:        9999,//右边限制
        mxTop:            0,//上边限制
        mxBottom:        9999,//下边限制
        mxContainer:    "",//指定限制在容器内
        LockX:            false,//是否锁定水平方向拖放
        LockY:            false,//是否锁定垂直方向拖放
        Lock:            false,//是否锁定
        Transparent:    false,//是否透明
        onStart:        function(){},//开始移动时执行
        onMove:            function(){},//移动时执行
        onStop:            function(){}//结束移动时执行
    };
    Extend(this.options, options || {});
  },

因此个人给出解决方案:
  //initialize方法
  this.options = SetOptions(options);
  //SetOptions方法
  SetOptions: function(options) {
   options = options || {};
		for(var method in this._checkOptions){
			options[method] = this._checkOptions[method](options[method]);
		}
		return options;
  }

 //新添加的方法
 _checkOptions:{
  Limit:function(val){
   if(typeof val == 'undefined'){
    return true;
   }
   return !!val;
  },
  mxLeft:function(val){
    return val&&parseInt(val)||0;
  }
  ... ...
 }


这样的话,参数的修正和初始化都统一在了一起。
在方法中直接操作this.options.xxx就可以了,如果觉得长,可以起个别名。如:
var os = this.options;
os.xxx;

 回复 引用 查看   
#126楼[楼主] 2010-11-01 17:51 cloudgamer      
@笨蛋的座右铭
这样结构不是很清晰

 回复 引用 查看   
#127楼 2010-11-01 19:27 笨蛋的座右铭      
@cloudgamer
这就知道你会提出这个问题

关于这个问题的答案,还是仁者见仁,智者见智了!

 回复 引用 查看   
#128楼 2010-11-05 14:37 咖啡不苦      
最近写一个dialog插件要写拖动效果,就遇到不少问题,现在滚动和iframe的问题依然没解决 望赐教
 回复 引用 查看   
#129楼[楼主] 2010-11-05 14:55 cloudgamer      
@咖啡不苦
这些确实不好解决
遇到什么问题了呢

 回复 引用 查看   
#130楼 2010-12-10 19:54 辕门      
引用博主:good


 回复 引用   
#131楼 2011-06-10 16:37 hanyuxinting[未注册用户]
simpledrag 的时候鼠标还是会出现 禁止状态。不过它是可以拖拽的。
 回复 引用   
#132楼 2011-07-05 18:06 为谁感动[未注册用户]
为什么jQuery引进来后不能用jQuery了
 回复 引用 查看   
#133楼 2011-08-15 17:16 呦菜      
楼主最近肿么没有新文章~~~
 回复 引用 查看   
#134楼 2011-09-02 16:50 恋冰魍魉魑魅      
Limit:false,//是否设置范围限制(为true时下面参数有用,可以是负数)
mxLeft:0,//左边限制
mxRight:9999,//右边限制
mxTop:0,//上边限制
mxBottom:9999,//下边限制

不是说可以设置负数吗?

为什么 我 Limit:true; 了
然后 mxLeft: - 100;
没效果呢??????

 回复 引用 查看   
#135楼 2011-09-09 22:08 hua_js      
楼主,我稍微改了一下你写的简单拖动效果代码,结果不正确了。改动如下:
this._fM = BindAsEventListener(this, this.Move);
this._fS = Bind(this, this.Stop);
这两句不要。。
在Start和Stop函数内,给document添加和删除事件委托时,直接这样写:
addEventHandler(document, "mousemove", BindAsEventListener(this, this.move));
addEventHandler(document, "mouseup", Bind(this, this.Stop));

removeEventHandler(document, "mousemove", BindAsEventListener(this, this.move));
removeEventHandler(document, "mouseup", Bind(this, this.Stop));
结果拖动元素时能移动,但是松开鼠标时,元素放不下,还粘在鼠标上。
这是什么问题?我觉得完全等效的代码,怎么会运行不正确??麻烦楼主解释解释,谢谢了。。。

 回复 引用 查看   
#136楼 2012-01-14 01:36 乌鸦L      
楼主啊 你下面的代码让我完全不知道怎么理解
麻烦楼主帮忙解答一下 为啥要这样写
var Class = {
create: function() {
return function() { this.initialize.apply(this, arguments); }
}
}

评论共2页: 上一页 1 2 
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1334778 yyF/ZRE/Yi0=