>  기사  >  웹 프론트엔드  >  자바스크립트 드래그 앤 드롭 효과에 대한 심층 연구

자바스크립트 드래그 앤 드롭 효과에 대한 심층 연구

黄舟
黄舟원래의
2016-12-14 16:05:39806검색

드래그 앤 드롭이라고도 불리는 드래그 앤 드롭 효과는 가장 일반적인 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)

은 끌기 프로그램입니다. 각각 프로그램.
드래그 앤 드롭 개체의 위치는 절대 위치여야 합니다.

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

마지막으로 시작 드래그 앤 드롭을 바인딩합니다. -드래그 앤 드롭 객체 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 stop 드래그 프로그램을 문서의 mousemove 및 mouseup 이벤트에 바인딩합니다. 각각:

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

문서에 바인딩하면 이벤트는 전체 창 문서에서 유효합니다.

문서 위에서 마우스를 움직이면 이동 프로그램이 실행됩니다. 드래그를 구현하는 프로그램은 다음과 같습니다.
드래그 앤 드롭 객체에 설정해야 할 왼쪽과 위쪽은 마우스의 현재 좌표값과 드래그가 시작될 때 마우스의 상대 좌표값의 차이로 얻을 수 있습니다.

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

마침내 마우스를 놓으면 정지 프로그램이 종료됩니다.
여기서 주요 기능은 시작 프로그램에서 문서에 추가된 이벤트를 제거하는 것입니다.

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

이런 간단한 드래그 앤 드롭 프로그램이 준비되어 있습니다. 다른 확장 프로그램과 세부 사항에 대해 이야기해 보겠습니다.

드래그 앤 드롭 잠금

잠금에는 가로 잠금(LockX), 세로 잠금(LockY), 전체 잠금(Lock)의 세 가지 유형이 있습니다.
이건 비교적 간단합니다. 가로,세로 방향을 잠그려면 Move에서 잠겼는지 확인하고 왼쪽과 위쪽으로 설정하면 됩니다. 완전히 잠겼으면 바로 돌아가면 됩니다.

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

트리거 개체

트리거 개체는 드래그 앤 드롭 프로그램을 트리거하는 데 사용됩니다. 트리거링에 드래그 앤 드롭 개체 전체를 사용할 필요가 없는 경우도 있습니다.
트리거 개체를 사용한 후에도 드래그 앤 드롭 개체는 계속 이동하지만 트리거 개체는 드래그 앤 드롭을 트리거하는 데 사용됩니다(일반적인 용도는 트리거 개체를 드래그 앤 드롭 개체에 넣는 것입니다).

범위 제한

범위 제한을 설정하려면 먼저 제한을 true로 설정해야 합니다. 범위 제한에는 고정 범위와 컨테이너 범위 제한 두 가지가 있으며 주로 Move 프로그램에서 설정합니다.
비교값이 범위를 초과할 경우 드래그 앤 드롭 개체가 설정 범위 내에 남을 수 있도록 왼쪽과 위쪽에 설정할 값을 수정하는 것이 원칙입니다.

고정 범위 제한

컨테이너 범위 제한은 상하좌우 드래그 앤 드롭 범위를 지정하는 것입니다.
각 속성의 의미는 다음과 같습니다.

상단(mxTop): 상한

하단(mxBottom): top+offsetHeight 제한;

왼쪽(mxLeft) : 왼쪽 제한;

오른쪽(mxRight): 왼쪽+offsetWidth 제한.

범위를 잘못 설정하면 상하 또는 좌우가 동시에 범위를 초과할 수 있습니다. 프로그램에 범위 매개변수를 수정하는 수리 프로그램이 있습니다.
복구 프로그램은 프로그램 초기화 및 시작 프로그램에서 실행됩니다. 복구 프로그램에서 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中,因为程序中是使用相对定位来设置容器范围限制的(如果是在容器外就要用绝对定位,这样处理就比较麻烦了),还有就是容器空间要比拖放对象大,这个就不用说明了吧。 
原理跟固定范围限制差不多,只是范围参数是根据容器的属性的设置的。

当设置了容器,会自动把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>
🎜>
안타깝게도 권위 있는 정보가 없어 추측만 할 수 밖에 없습니다. 아직 이해하지 못하는 부분이 많으므로 나중에 공부하겠습니다.

ff2 아래 마우스 캡처에 버그가 있습니다. 드래그 앤 드롭 개체 내부에 텍스트 내용이 없고 브라우저 외부로 드래그 앤 드롭하면 캡처가 실패합니다.

 와 같은 빈 텍스트를 드래그 앤 드롭 개체에 삽입하세요. 이 문제는 해결될 수 있지만 이 버그는 ff3에서 수정되었습니다.


포커스 상실

일반적인 상황에서는 마우스 캡쳐를 하면 정상적으로 이벤트 캡쳐가 가능하지만, 브라우저 창의 포커스가 상실되면 캡쳐가 실패하게 됩니다.

포커스 손실을 유발할 수 있는 작업에는 창 전환(Alt+Tab 포함), 경고 및 팝업 및 기타 팝업 양식이 포함된다는 사실을 임시 테스트했습니다.

포커스가 손실되면 Stop 프로그램을 동시에 실행하여 드래그 앤 드롭을 종료해야 합니다. 그러나 포커스가 손실되면 mouseup 이벤트를 캡처할 수 없습니다. 즉, _fS가 트리거될 수 없습니다.
다행히 IE에는 캡처가 실패할 때 트리거되는 onlosecapture 이벤트가 있습니다.

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

Stop 프로그램에서 이를 제거합니다:

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

그러나 ff에는 비슷한 방법이 없습니다. , 그러나 muxrwc가 losscapture .onblur 이벤트를 대체하는 창을 찾았다면 시작 프로그램에서 이를 설정할 수 있습니다:

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

제거 Stop 프로그램에서:

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

그러면 IE에도 window.onblur 이벤트가 있으므로 losscapture 대신 window.onblur를 사용하면 코드 조각.

그런 다음 몇 가지 테스트를 수행한 결과 기본적으로 losscapture를 실행하는 모든 상황은 window.onblur를 동시에 실행하는 것으로 나타났습니다.

그래서 losscapture 대신 window.onblur를 사용하도록 프로그램을 수정했는데, 테스트 후 문제가 발생했습니다. 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 손실이 발생한다고 하는데 ff3의 테스트에서는 발견되지 않았습니다. 혹시 mouseup 손실이 발견되면 반드시

선택 취소

즉, setCapture 선택 설정 후의 콘텐츠는 금지되지만, 설정 전에 선택되었던 콘텐츠는 삭제되지 않으며, 설정 후 Ctrl+a를 눌러 선택하는 등

다른 방법으로도 콘텐츠를 선택할 수 있습니다. 콘텐츠.

ps: onkeydown, onkeyup 및 onkeypress 이벤트는 마우스 캡처의 영향을 받지 않습니다.

그리고 ff는 마우스를 누를 때 원래 선택한 콘텐츠를 지울 수 있지만 마우스를 드래그하고 Ctrl+A를 누르면 계속 콘텐츠가 선택됩니다.
그러나 시스템 기본 동작을 삭제한 후에는 이 선택이 드래그 앤 드롭 작업에 영향을 미치지 않습니다. 여기서 설정은 주로 더 나은 경험을 위한 것입니다.

과거에는 목적을 달성하기 위해 드래그 앤 드롭 개체 선택을 비활성화하는 방법, 즉 IE에서 드래그 앤 드롭 개체의 onselectstart를 false로 설정하고 스타일을 설정하는 방법을 사용했습니다. FF의 MozUserSelect(css:-moz-user-select)는 없음입니다.
그러나 이 방법은 드래그 앤 드롭 개체 자체가 선택되는 것을 방지할 수 있을 뿐입니다. 나중에 선택을 취소하는 더 좋은 방법을 찾았는데, 이는 드래그 앤 드롭 개체의 선택 효과에 영향을 주지 않을 뿐만 아니라 전체 문서를 지웁니다:

ie: document.selection.empty()

ff: window.getSelection().removeAllRanges()

드래그 앤 드롭 과정에서 콘텐츠가 선택되는 것을 방지하려면 Move 프로그램에 넣으세요. 호환되는 작성 방법은 다음과 같습니다.

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

margin

드래그 앤 드롭 객체에 여백이 설정된 경우 드래그 앤 드롭할 때 정렬이 잘못되는 또 다른 상황이 있습니다. 삭제(SimpleDrag 드래그 앤 드롭 개체에 대한 여백 설정이 테스트될 수 있음).
이유는 시작프로그램에서 _x, _y 설정시 오프셋을 사용하는데 이 값에는 마진이 포함되어 있기 때문에 왼쪽과 위쪽을 설정하기 전에 이 마진을 빼야 합니다.
그러나 시작 프로그램에서 여백을 제거하면 이동 프로그램에서 범위 제한을 설정할 때 계산 오류가 발생합니다.
따라서 시작 프로그램에서 값을 얻는 것이 가장 좋습니다.

this._marginLeft =parseInt(CurrentStyle(this.Drag).marginLeft) || 0; 최종 스타일을 가져오는 데 사용됩니다. 자세한 내용은 여기의 최종 스타일 섹션을 참조하세요.

Move 프로그램에서 값 설정:

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

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

범위를 수정한 후 여백을 설정해야 합니다. 그렇지 않으면 정렬이 어긋나게 됩니다.

[투명한 배경 버그]

IE에는 투명한 배경 버그가 있습니다(버그인지는 모르겠습니다). 다음 코드를 사용하여 테스트할 수 있습니다.





배경을 클릭하면 이벤트가 실행될 수 없지만 여전히 실행될 수는 있습니다. 테두리를 클릭하면 실행됩니다.
왜요? 그런 다음 다음 코드를 사용하여 테스트합니다.










다음과 같은 대략적인 아이디어를 얻을 수 있어야 합니다. 두 개의 div가 너머에 있습니다. 신체의 일부(즉, 빨간색 상자 너머)는 이벤트를 트리거할 수 없습니다.
즉, 이벤트를 유발하는 지점이 신체 외부에 있고 배경이 투명할 경우 트리거 지점이 신체 외부의 빈 공간에 있는 것으로 잘못 인식되므로 이벤트가 발생할 수 없습니다. 트리거되었습니다.

해결책은 이벤트 트리거 지점을 몸체 내에 유지하거나 배경을 불투명하게 설정하는 것입니다.


프로그램에서는 드래그 앤 드롭 개체에 대한 배경색만 설정하면 되지만 때로는 투명해야 하는 경우(예: 자르기 효과)가 필요한 경우 어떻게 해야 할까요?
가장 먼저 떠오르는 것은 배경색을 추가하고 완전히 투명하게 설정하는 것인데, 그러면 테두리나 컨테이너에 있는 객체 등도 완전히 투명해져서 좋지 않습니다.

제가 생각한 해결책은 컨테이너 내부에 레이어를 추가하여 전체 컨테이너를 덮고 배경색과 완전한 투명도를 설정하는 것입니다.


with(this._Handle.appendChild(document.createElement(" div" )).style){
width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)";

}


이 버그가 발생하면 프로그램의 선택적 매개변수 Transparent를 true로 설정하면 해당 레이어가 자동으로 삽입됩니다.
더 좋은 방법이 있으면 조언 부탁드립니다.

당분간은 그렇습니다. 다만, 고려되지 않은 iframe, 스크롤링 등이 있을 수 있으니 나중에 필요하면 연구해보겠습니다.

더 많은 관련 글은 PHP 중국어 홈페이지(www.php.cn)를 주목해주세요!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.