首頁  >  文章  >  web前端  >  javascript拖放效果深入研究

javascript拖放效果深入研究

黄舟
黄舟原創
2016-12-14 16:05:39759瀏覽

拖放效果,也叫拖拽,學名Drag-and-drop ,是最常見的js特效之一。
如果忽略很多細節,實作起來很簡單,但往往細節才是困難所在。
這個程式的原型是在做圖片切割效果的時候做出來的,那時參考了好幾個同類的效果,跟muxrwc和BlueDestiny學習了不少東西。
雖然每次整理都覺得很好了,不過每隔一段時間又會發現得某個地方可以改善,某個地方有錯誤,某些需求需要實現,就像自己學習的知識那樣。

這裡考慮到有的人可能只需要簡單的拖放,所以有一個 簡化版的拖放SimpleDrag ,方便學習。

程式原理

這裡以SimpleDrag為例說一下基本原理。

首先初始化程式中要一個拖放物件:

this.Drag = $(drag);

還要兩個參數在開始時記錄滑鼠相對拖放物件的座標:

this._x = this. _y = 0;

還有兩個事件物件函數用來新增移除事件:

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

分別是拖曳程序和停止拖曳程序。
拖放物件的position必須是absolute絕對定位:

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

最後把Start開始拖曳程式綁定到拖曳物件mousedown事件:

addEventHandler(this. Drag, "mousedown", BindAsEventListener(this, this.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 = ...; }


觸發物件

觸發物件是用來觸發拖放程式的。有的時候不需要整個拖放物件都用來觸發,這時就需要觸發物件了。 

使用了觸發對象後,進行移動的還是拖放對象,只是用觸發對象來觸發拖放(一般的使用是把觸發對象放到拖放對象裡面)。


範圍限制

要設定範圍限制必須先把Limit設為true。範圍限制分兩種,分別是固定範圍和容器範圍限制,主要在Move程式中設定。 

原理是當比較的值超過範圍時,修正left和top要設定的值使拖放物件保持在設定的範圍內。


固定範圍限制

容器範圍限制就是指定上下左右的拖放範圍。

各個屬性的意思是:


上(mxTop):top限制;

下(mxBottom):top+offsetHeight限制;

左(mxLeft):left限制

如果範圍設定不正確,可能導致上下或左右同時超過範圍的情況,程式中有一個Repair程式用來修正範圍參數的。

Repair程式會在程式初始化和Start程式中執行,在Repair程式修正mxRight和mxBottom:

this.mxRight = Math.max(this.mxRight, this.mxLeft + this.Drag.offsetWidth); . 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, mxBot this.Drag.offsetHeight), mxTop);

對於左邊上邊要取更大的值,對於右邊下面就要取更小的值。

容器范围限制

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

当设置了容器,会自动把position设为relative来相对定位:

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

注意relative要在获取offsetLeft和offsetTop即设置_x和_y之前设置,offset才能正确获取值。

由于是相对定位,对于容器范围来说范围参数上下左右的值分别是0、clientHeight、0、clientWidth。

clientWidth和clientHeight是容器可视部分的宽度和高度(详细参考这里)。 
为了容器范围能兼容固定范围的参数,程序中会获取容器范围和固定范围中范围更小的值:

mxLeft = Math.max(mxLeft, 0); 
mxTop = Math.max(mxTop, 0); 
mxRight = Math.min(mxRight, this._mxContainer.clientWidth); 
mxBottom = Math.min(mxBottom, this._mxContainer.clientHeight);

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

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

鼠标捕获

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

this._Handle.setCapture();

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

 
 

mouseover
 
<script>document.body.setCapture(); </script> 
 

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

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

this._Handle.releaseCapture();

setCapture是ie的鼠标捕获方法,对于ff也有对应的captureEvents和releaseEvents方法。 
但这两个方法只能由window来调用,而且muxrwc说这两个方法在DOM2里已经废弃了,在ff里已经没用了。 
不过ff里貌似会自动设置取消鼠标捕获,但具体的情形就不清楚了,找不到一个比较详细的介绍,谁有这方面的资料记得告诉我啊。

下面都是我的猜测,ff的鼠标捕获相当于能自动设置和释放的document.body.setCapture(false)。 
因为我测试下面的程序,发现ie和ff效果是差不多的: 
ie:

 
 

 
<script> <br>document.body.onmousedown=function(){this.setCapture(false)} <br>document.body.onmouseup=function(){this.releaseCapture()} <br>document.onmousemove=function(){aa.innerHTML+=1} <br></script> 
 

ff:

 
 

 
<script> <br/>document.onmousemove=function(){aa.innerHTML+=1} <br/></script> 

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn