拖放效果,也叫拖拽,學名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 = ...; }
觸發物件
觸發物件是用來觸發拖放程式的。有的時候不需要整個拖放物件都用來觸發,這時就需要觸發物件了。
使用了觸發對象後,進行移動的還是拖放對象,只是用觸發對象來觸發拖放(一般的使用是把觸發對象放到拖放對象裡面)。
範圍限制
要設定範圍限制必須先把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);
根據範圍參數修正移動參數:
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):
<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>
就只能猜猜沒有權威的資料參考就只能猜猜了,可惜還有很多還沒理解的地方以後再研究拉。
注意ff2下的滑鼠捕捉有一個bug,當拖放物件內部沒有文字內容並拖放到瀏覽器外時捕獲就會失效。
給拖放物件插入一個空白文本,例如 就可以解決,不過這個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代替window.onblur代替losecture不就可以省一段程式碼了嗎。
接著我做了一些測試,發現基本上觸發losecapture的情況都會同時觸發window.onblur,看來真的可以。
於是我修改程式用window.onblur取代losecapture,但測試後就出問題了,我發現如果我用alt+tab切換到另一個窗口,拖曳還可以繼續,但這個時候應該是已經遺失焦點了。
於是我逐一排除測試和程式碼,結果發現如果使用了DTD,那麼window.onblur會在再次獲得焦點時才會觸發。
大家可以用下面這段程式碼測試:
<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丟失的情況,但我在ffff33中測沒有發現,如果各位發現任何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),可以用下面的程式碼測試:
會發現背景點擊觸發不了事件,不過點擊邊框的話還是可以點擊邊框的話還是可以點擊邊框的話還是可以點擊邊框觸發。 為什麼呢?再用下面的程式碼測試:

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

Dreamweaver Mac版
視覺化網頁開發工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SublimeText3漢化版
中文版,非常好用