Home  >  Article  >  Web Front-end  >  In-depth study of javascript drag and drop effect

In-depth study of javascript drag and drop effect

黄舟
黄舟Original
2016-12-14 16:05:39761browse

The drag-and-drop effect, also called drag-and-drop, is one of the most common js special effects.
If you ignore many details, it is very simple to implement, but the details are often the difficulty.
The prototype of this program was created when I was doing image cutting effects. At that time, I referred to several similar effects and learned a lot from muxrwc and BlueDestiny.
Although I feel good every time I sort it out, every once in a while I will find that somewhere can be improved, there is an error somewhere, and certain needs need to be realized, just like the knowledge I have learned.

Considering that some people may only need simple drag and drop, there is a simplified version of drag and drop SimpleDrag to facilitate learning.

Program Principle

Here we take SimpleDrag as an example to explain the basic principles.

First, a drag-and-drop object is required in the initialization program:

this.Drag = $(drag);

There are also two parameters to record the coordinates of the mouse relative to the drag-and-drop object at the beginning:

this._x = this. _y = 0;

There are also two event object functions for adding and removing events:

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

They are dragging program and stop dragging program respectively.
The position of the drag-and-drop object must be absolute positioning:

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

Finally, bind the Start drag-and-drop program to the mousedown event of the drag-and-drop object:

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

When the mouse is pressed on the drag and drop object, the Start program will be triggered. It is mainly used to prepare for dragging. Here, the coordinates of the mouse relative to the drag and drop object are recorded. :

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

And bind the _fM drag program and _fS stop drag program respectively Binding mousemove and mouseup events to document:

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

Binding to document can ensure that the event occurs throughout the window are valid in the document.

When the mouse moves on the document, the Move program will be triggered. Here is the program to implement dragging.
The left and top that should be set for the drag and drop object can be obtained by the difference between the current coordinate value of the mouse and the relative coordinate value of the mouse when dragging started:

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

After finally releasing the mouse, the Stop program is triggered to end the drag and drop.
The main function here is to remove the events added to the document in the Start program:

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

This way A simple drag-and-drop program is ready. Let’s talk about other extensions and details.

Drag and drop lock

There are three types of locks, namely: horizontal lock (LockX), vertical lock (LockY), and complete lock (Lock).
This is relatively simple. To lock the horizontal and vertical directions, you only need to judge whether it is locked in Move and then set left and top. If it is fully locked, just return directly.

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

Trigger object

The trigger object is used to trigger the drag-and-drop program. Sometimes the entire drag-and-drop object does not need to be used for triggering. In this case, the trigger object is needed.
After using the trigger object, the drag and drop object is still moved, but the trigger object is used to trigger the drag and drop (the general use is to put the trigger object into the drag and drop object).

Scope Limit

To set the range limit, you must first set Limit to true. There are two types of range restrictions, namely fixed range and container range restrictions, which are mainly set in the Move program.
The principle is that when the compared value exceeds the range, the values ​​to be set for left and top are corrected so that the drag and drop object can remain within the set range.

Fixed range limit

Container range limit is to specify the drag and drop range of the top, bottom, left, and right.
The meaning of each attribute is:

upper (mxTop): top limit;

lower (mxBottom): top+offsetHeight limit;

left (mxLeft): left limit;

right (mxRight): left+offsetWidth limit .

If the range is set incorrectly, it may cause the up and down or left and right to exceed the range at the same time. There is a Repair program in the program to correct the range parameters.
The Repair program will be executed in the program initialization and Start program. Correct mxRight and mxBottom in the Repair program:

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

where mxLeft+offsetWidth and mxTop+offsetHeight are the minimum range values ​​of mxRight and mxBottom respectively.

Correct the movement parameters according to the range parameters:

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

Take a larger value for the upper left side and a smaller value for the lower right side.

容器范围限制

容器范围限制的意思就是把范围限制在一个容器_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>

Unfortunately, there is no authoritative information reference. Guess, there are still many things that I haven’t understood yet and I’ll study them later.

Note that there is a bug in mouse capture under ff2. When the drag-and-drop object has no text content inside and is dragged and dropped outside the browser, the capture will fail.
Insert an empty text into the drag and drop object, such as   This can be solved, but this bug has been fixed in ff3.

Focus lost

Under normal circumstances, mouse capture can capture events normally, but if the focus of the browser window is lost, the capture will fail.
I have temporarily tested that operations that can cause focus loss include switching windows (including alt+tab), alert and popup and other pop-up forms.
When the focus is lost, the Stop program should be executed at the same time to end the drag and drop. However, when the focus is lost, the mouseup event cannot be captured, that is, _fS cannot be triggered.
Fortunately, IE has an onlosecapture event that will be triggered when the capture fails. For this situation, you can set it like this:

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

and remove it in the Stop program:

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

But ff does not have a similar method, but muxrwc finds a window.onblur event that replaces losecapture, then you can set it in the Start program:

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

Remove in the Stop program:

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

That ie also has a window.onblur event, then use window.onblur instead of losecapture Wouldn't it save a piece of code?
Then I did some tests and found that basically any situation that triggers losecapture will trigger window.onblur at the same time, which seems to work.
So I modified the program to use window.onblur instead of losecapture, but something went wrong after testing. I found that if I used alt+tab to switch to another window, dragging could continue, but the focus should have been lost at this time.

So I eliminated the test and program code one by one, and found that if DTD is used, window.onblur will not be triggered until it gains focus again.
You can use the following code to test:


<script>window.onblur=function(){alert(1)} </script>

Window.onblur will only be triggered after switching to other programs and then switching back. There are a few more weird situations that I won’t mention. Anyway, using window.onblur in IE is not ideal.

Default action

Dragging and dropping the text content, connections and pictures in the selected state will trigger the system's default action. For example, dragging the mouse on a picture in IE will become a prohibited operation state, which will cause the drag and drop program to be executed. fail.

However, after setCapture is set in IE, drag-and-drop operations and content selection with the mouse through the user interface will be prohibited.
It means that after setCapture, you cannot drag and drop and select the document content. Note that drag and drop here refers to the default action of the system. For example, ondragstart will not be triggered.
However, if the parameter of setCapture is false, the object in the container can still trigger the event (see the mouse capture part for details), so the parameter of setCapture should be set to true or keep the default value.

The mouse capture of ff does not have this function, but it can be solved by using preventDefault to cancel the default action of the event:

oEvent.preventDefault();

ps: It is said that using preventDefault will cause mouseup loss, but I am in ff3 We haven't found any in the test. If you find any mouseup loss, please tell me.

Clear selection

ie. After setting setCapture, content selection will be prohibited, but therefore the content that has been selected before setting will not be cleared. And after setting, you can also select content through other methods,
For example, use ctrl+a to select content.
ps: onkeydown, onkeyup and onkeypress events will not be affected by mouse capture.
And ff can clear the original selected content when mousedown, but drag the mouse and ctrl+a will still continue to select the content.
However, after discarding the system default action, this choice will not affect the drag and drop operation. The setting here is mainly for a better experience.

In the past, I used the method of disabling the selection of drag and drop objects to achieve the goal, that is, setting the onselectstart of the drag and drop object in IE to return false, and setting the style MozUserSelect (css:-moz-user-select) to none in FF.
But this method can only prevent the drag-and-drop object itself from being selected. Later, I found a better way to clear the selection, which not only does not affect the selection effect of the drag-and-drop object, but also clears the entire document:

ie: document.selection .empty()

ff: window.getSelection().removeAllRanges()

In order to prevent content from being selected during the drag and drop process, put it into the Move program. The following is the compatible writing method:

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

margin

There is another situation. When the margin is set on the drag and drop object, it will be misaligned when dragging and dropping (you can test it by setting margin on the SimpleDrag drag and drop object).
The reason is that offset is used when setting _x and _y in the Start program, and this value includes margin, so this margin must be subtracted before setting left and top.
But if margin is removed in the Start program, there will be a calculation error when setting the range limit in the Move program,
So it is best to get the value in the Start program:

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

where CurrentStyle is used to obtain the final style. See the final style section here for details.

Set the value in the Move program:

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

It should be noted that the margin must be set after the range is corrected, otherwise it will be misaligned.

【Transparent background bug】

There is a transparent background bug in IE (I don’t know if it is a bug). You can use the following code to test it:






You will find that the background click triggers The event cannot be triggered, but it can still be triggered by clicking on the border.
Why? Then use the following code to test:






< /div>


< /body>

You should be able to get a rough idea. The following two divs cannot trigger events if they exceed the body (that is, exceed the red box).
That is to say, when the point that triggers the event is outside the body, and the background is transparent, then it will be mistakenly thought that the trigger point is in a blank place outside the body, so the event cannot be triggered.
The solution is to keep the event trigger point within the body, or set a non-transparent background.

The problem can be solved by just setting a background color for the drag and drop object in the program, but sometimes the requirement is to be transparent (such as cutting effect), so what should I do?
The first thing that comes to mind is to add a background color and set it to be completely transparent, but then even the borders and objects in the container are completely transparent, which is not good.
A solution I thought of is to add a layer inside the container, cover the entire container, and set the background color and complete transparency:

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

When you find that this bug appears in the program, set the program optional parameter Transparent to true. Such a layer will be automatically inserted.
If you have a better method, please give me some advice.

That’s it for the time being, but there are iframes, scrolling, etc. that I haven’t considered yet. I’ll look into them later if necessary.

For more related articles, please pay attention to the PHP Chinese website (www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn