1. 程式人生 > >拖拽效果詳細解讀

拖拽效果詳細解讀

拖放效果,也叫拖拽,學名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));

滑鼠在拖放物件按住,就會觸發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限制;

右(mxRight):left+offsetWidth限制。

如果範圍設定不正確,可能導致上下或左右同時超過範圍的情況,所以要在Start程式中進行修正:

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中,因為程式中是使用相對定位來設定容器範圍限制的(如果是在容器外就要用絕對定位,這樣處理就比較麻煩了),還有就是容器空間要比拖放物件大,這個就不用說明了吧。

原理跟固定範圍限制差不多,只是範圍引數是根據容器的屬性的設定的。

當設定了容器,會自動把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時會發生移位現象,所以儘量避免沒有設定relative而又設定了拖放物件left和top,才執行程式的情況。

不過也有一個取巧的方法就是設定margin來代替定位(使用margin的話會自動修正,後面說明)。

因為設定相對定位的關係,容器_mxContainer設定過後一般不要取消或修改,否則很容易造成移位異常。

【滑鼠捕獲】

我在一個拖放例項中看到,即使滑鼠移動到瀏覽器外面,拖放程式依然能夠執行,仔細檢視後發現是用了setCapture。

滑鼠捕獲(setCapture)是這個程式的重點,作用是將滑鼠事件捕獲到當前文件的指定的物件。這個物件會為當前應用程式或整個系統接收所有滑鼠事件。

使用很簡單:

this._Handle.setCapture();

setCapture捕獲以下滑鼠事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout。

程式中主要是要捕獲onmousemove和onmouseup事件。

msdn的介紹中還說到setCapture有一個bool引數,用來設定在容器內的滑鼠事件是否都被容器捕獲。

容器就是指呼叫setCapture的物件,大概意思就是:

引數為true時(預設)容器會捕獲容器內所有物件的滑鼠事件,即容器內的物件不會觸發滑鼠事件(跟容器外的物件一樣);

引數為false時容器不會捕獲容器內物件的滑鼠事件,即容器內的物件可以正常地觸發事件和取消冒泡。

而對於容器外的滑鼠事件無論引數是什麼都會被捕獲,

可以用下面這個簡單的例子測試一下(ie):

<html>

<body onclick="alert(2)">

<div onmousemove="alert(1)">mouseover </div>

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

</body>

</html>

這裡的引數是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:

<html>

<body>

<div id="aa" onmouseover="alert(1)"> </div>

<script>

document.body.onmousedown=function(){this.setCapture(false)}

document.body.onmouseup=function(){this.releaseCapture()}

document.onmousemove=function(){aa.innerHTML+=1}

</script>

</body>

</html>

ff:

<html>

<body>

<div id="aa" onmouseover="alert(1)"> </div>

<script>

document.onmousemove=function(){aa.innerHTML+=1}

</script>

</body>

</html>

可惜沒有權威的資料參考就只能猜猜了,還有很多還沒有理解的地方以後再研究拉。

注意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();

【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;

其中CurrentStyle是用來獲取最終樣式,詳細看這裡的最終樣式部分

在Move程式中設定值:

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

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

要注意margin要在範圍修正只後再設定,否則會錯位。

【透明背景bug】

在ie有一個透明背景bug(不知算不算bug),可以用下面的程式碼測試:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<body>

<div onmousedown="alert(1)" style="border:10px solid #C4E3FD; width:50px; height:50px;position:absolute;"> </div>

</body>

</html>

會發現背景點選觸發不了事件,不過點選邊框的話還是可以觸發。

為什麼呢?再用下面的程式碼測試:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<body style="border:1px solid #FF0000;">

<style>div{width:100px; height:100px; border:1px solid #000;} </style>

<div style="position:relative;">

<div onclick="alert(1)" style="border-color:#00f;margin:50px;"> </div>

<div onclick="alert(2)" style="border-color:#6f0;position:absolute;top:50px;"> </div>

</div>

</body>

</html>

應該能看出個大概了,下面兩個div超出body(即超出紅色框)的部分就觸發不了事件。

也就是說當觸發事件的點,在body以外,而背景又是透明的,那麼就會誤認為觸發點是在了body外空白的地方,所以觸發不了事件。

那解決的方法就是,使事件觸發點保持在body內,或者設定一個非透明背景。

那程式中只要給拖放物件設一個背景色就可以解決了,但有時需求正好是要透明(例如切割效果),那怎麼辦呢?

首先想到的是加上背景色後設置完全透明,但這樣連邊框,容器內的物件等都完全透明瞭,這個不好。

我想到的一個解決方法是在容器裡面加一個層,覆蓋整個容器,並設定背景色和完全透明:

with(this._Handle.appendChild(document.createElement("div")).style){

width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)";

}

當發現程式有這個bug出現,把程式可選引數Transparent設為true就會自動插入這樣一個層了。

各位如果有更好的方法請多多指點。

暫時就研究到這裡,不過還有iframe,滾屏等這些還沒考慮到,等以後有需要了再來研究拉。

相關推薦

效果詳細解讀

拖放效果,也叫拖拽,學名Drag-and-drop ,是最常見的js特效之一。 如果忽略很多細節,實現起來很簡單,但往往細節才是難點所在。 這個程式的原型是在做圖片切割效果的時候做出來的,那時參考了好幾個同類的效果,跟muxrwc和BlueDestiny學習了不少東西。 雖然每次整理都覺得很好了,不過

js效果詳細講解

設置 物體 tcap this absolut ansi fse .get content <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xht

JavaScript實現網頁元素的效果

wid parseint fontsize current ini .net win == oct 以下的頁面中放了兩個div,能夠通過鼠標拖拽這兩個元素到任何位置。 實現該效果的HTML頁面代碼例如以下所看到的: <!DOCTYPE html> &

canvasn效果

鼠標 fun brush int eat 初始 ntb 代碼 class canvas拖拽和平時用的js拖拽是有區別的 普通的js是設置目標為絕對定位,再根據鼠標的移動來改變left和top的值 canvas是獲得了鼠標的位置,直接在目標點進行重新繪制 下面給一個簡單的拖拽

js實現鼠標的效果

郵箱 gin start ott borde mouse ora mar mooc 拖拽效果在我們上網的過程中是很常見的,大家都應該在電腦上面登陸過qq吧,當這個qq的登陸框彈出來的時候,我們是可以進行拖動的。這就是一個拖拽效果 這是我在慕課網上面看到的,我直接拿過來了,地

JavaScript實現最簡單的效果

stop 效果展示 title 另存為 -h 通過 沒有 軟件 .cn 一、一些無關痛癢的嘮叨 拖拽還是挺不錯的一個頁面效果,我個人認為,其生命力在於可以讓用戶自己做一些操作,所謂自定義。例如: ①瀏覽器標簽順序的拖拽切換 現在基本上所有的選項卡式的瀏覽器都有順序拖拽切換的

適配 移動 pc 效果

適配 mov art 移動 log pos eve () color var flag = false; var cur = { x:0, y:0 } var nx,ny,dx,dy,x,y ;

原生js效果

scrip char back width eve top abs box document <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8">

JS移動客戶端--觸屏滑動事件及js手機效果

移動端觸屏滑動的效果其實就是圖片輪播,在PC的頁面上很好實現,繫結click和mouseover等事件來完成。但是在移動裝置上,要實現這種輪播的效果,就需要用到核心的touch事件。處理touch事件能跟蹤到螢幕滑動的每根手指。 以下是四種touch事件 touchstart: //手指放到螢幕上

移動端可效果

<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=

Android 露珠/水滴 效果實現

解釋一下標題: 露珠拖拽/水滴拖拽:就是一個View不但能跟著手滑動。還能根據滑動速度,改變形狀,像露珠或者水滴那樣。 這裡是效果實現的Demo 具體實現就是如下一個Java檔案 DewdropView.java package com.demo.dewdropdemo; i

js 滑鼠效果實現

 效果: 原始碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>速表拖拽效果實現</title>

js 鼠標效果實現

element width ima ado cti rem clas fun gif 效果: 源碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF

實現具有吸附性的效果-高階(相容)

1.body裡面有Div和一段文字 <body> 這是一段文字這是一段文字這是一段文字這是一段文字這是一段文字這是一段文字這是一段文字 <div id=“div1”>盒子裡面的文字盒子裡面的文字盒子裡面的文字盒子裡面的文字盒子裡面的文字</div>

HTML5效果

drag and drop(拖拽) 拖拽元素事件 : 事件物件為被拖拽元素 dragstart : 拖拽前觸發 drag :拖拽前、拖拽結束之間,連續觸發 dragend : 拖拽結束觸發 目標元素事件 : 事件物件為目標元素 dragenter : 進入目標元素觸發

基於Vue實現效果

效果圖 分清clientY pageY screenY layerY offsetY的區別 在我們想要做出拖拽這個效果的時候,我們需要分清這幾個屬性的區別,這幾個屬性都是計算滑鼠點選的偏移值,我們需要對其進行了解才可以繼續實現我們的拖拽效果 clientY 指的是距離可視頁面左上角

jQuery的ztree仿windows檔案新建和效果

前面的話:zTree 是一個依靠 jQuery 實現的多功能 “樹外掛”。優異的效能、靈活的配置、多種功能的組合是 zTree 最大優點。專門適合專案開發,尤其是 樹狀選單、樹狀資料。 ztree官方文件:http://www.treejs.cn/v3/api.php 想要實現的

Android 自定義View實現效果

騰訊QQ有那種紅點拖動效果,今天就來實現一個簡單的自定義View拖動效果,再回到原處,並非完全仿QQ紅點拖動 先來看一下效果圖 簡單說一下實現步驟 1.建立一個類繼承View 2.繪製出一個

C#如何實現對winform的選單進行效果的實現

關於GC分代回收的問題使用visualStudioC#時Debug功能無法正常執行關於GC分代回收的問題使用visualStudioC#時Debug功能無法正常執行 求教!如何對HtmlElement執行右鍵命令如圖片的設定為背景cprogram求教!如何對HtmlEleme

js 圖片效果實現

1、前端html排列 <div class="row" id="listing_extra_images" name="TophatterList[extra_images]"> <div class="image_item_content"