Heim > Artikel > Web-Frontend > Eingehende Untersuchung des Drag-and-Drop-Effekts von Javascript
Der Drag-and-Drop-Effekt, auch Drag-and-Drop genannt, ist einer der häufigsten js-Spezialeffekte.
Wenn man viele Details außer Acht lässt, ist die Umsetzung sehr einfach, aber oft sind es die Details, die die Schwierigkeit darstellen.
Der Prototyp dieses Programms wurde erstellt, als ich Bildschneideeffekte machte. Damals bezog ich mich auf mehrere ähnliche Effekte und lernte viel von muxrwc und BlueDestiny.
Obwohl ich mich jedes Mal gut fühle, wenn ich es in Ordnung bringe, werde ich hin und wieder feststellen, dass irgendwo etwas verbessert werden kann, irgendwo ein Fehler liegt und bestimmte Bedürfnisse verwirklicht werden müssen, genau wie das Wissen, das ich gelernt habe.
Da einige Leute möglicherweise nur einfaches Drag-and-Drop benötigen, gibt es eine vereinfachte Version von Drag-and-Drop SimpleDrag, um das Erlernen zu erleichtern.
Programmprinzip
Hier nehmen wir SimpleDrag als Beispiel, um die Grundprinzipien zu erklären.
Zuerst ist im Initialisierungsprogramm ein Drag-and-Drop-Objekt erforderlich:
this.Drag = $(drag);
Es gibt auch zwei Parameter, um das Relative aufzuzeichnen Ziehen und Ablegen der Maus am Anfang. Die Koordinaten des Objekts:
this._x = this._y = 0;
Es gibt auch zwei Ereignisobjektfunktionen zum Hinzufügen und Entfernen von Ereignissen :
this._fM = BindAsEventListener(this, this.Move);
this._fS = Bind(this, this.Stop);
sind das Ziehprogramm und das Stopp-Ziehen Programm bzw.
Die Position des Drag-and-Drop-Objekts muss eine absolute Positionierung sein:
this.Drag.style.position = "absolute";
Zuletzt binden Sie das Start-Drag-and-Objekt -Drop-Programm zum Drag-and-Drop-Objekt-Mousedown-Ereignis:
addEventHandler(this.Drag, "mousedown", BindAsEventListener(this, this.Start));
Das Startprogramm wird Wird ausgelöst, wenn die Maus auf das Drag-and-Drop-Objekt gedrückt wird. Wird hauptsächlich zur Vorbereitung des Ziehens verwendet. Hier werden die Koordinaten der Maus relativ zum Drag-and-Drop-Objekt aufgezeichnet:
this._x = oEvent. clientX - this.Drag.offsetLeft;
this._y = oEvent.clientY - this.Drag.offsetTop;
Und binden Sie das Drag-Programm _fM und das Stop-Drag-Programm _fS an die Mousemove- und Mouseup-Ereignisse des Dokuments bzw.:
addEventHandler(document, "mousemove" , this._fM);
addEventHandler(document, "mouseup", this._fS);
Binding to document kann sicherstellen, dass die Das Ereignis ist im gesamten Fensterdokument gültig.
Wenn sich die Maus über das Dokument bewegt, wird das Programm „Verschieben“ ausgelöst. Hier ist das Programm zum Implementieren des Ziehens.
Der linke und obere Wert, der für das Drag-and-Drop-Objekt festgelegt werden sollte, kann durch die Differenz zwischen dem aktuellen Koordinatenwert der Maus und dem relativen Koordinatenwert der Maus zu Beginn des Ziehens ermittelt werden:
dies .Drag.style.left = oEvent .clientX - this._x + "px";
this.Drag.style.top = oEvent.clientY - this._y + "px";
Nachdem endlich Durch Loslassen der Maus wird das Stop-Programm beendet.
Die Hauptfunktion hier besteht darin, die dem Dokument im Startprogramm hinzugefügten Ereignisse zu entfernen:
removeEventHandler(document, "mousemove", this._fM);
removeEventHandler(document, "mouseup"); , this._fS);
So ein einfaches Drag-and-Drop-Programm ist fertig.
Drag-and-Drop-Sperre
Es gibt drei Arten von Sperren, nämlich: horizontale Sperre (LockX), vertikale Sperre (LockY) und vollständige Sperre (Lock).
Um die horizontale und vertikale Richtung zu sperren, müssen Sie nur feststellen, ob es in „Verschieben“ gesperrt ist, und es dann nach links und oben einstellen. Wenn es vollständig gesperrt ist, kehren Sie einfach zurück.
if(!this.LockX){ this.Drag.style.left = ...; }
if(!this.LockY){ this.Drag.style.top = ...; }
Trigger-Objekt
Das Trigger-Objekt dient zum Auslösen des Drag-and-Drop-Programms. Manchmal muss nicht das gesamte Drag-and-Drop-Objekt zum Auslösen verwendet werden. In diesem Fall wird das Trigger-Objekt benötigt.
Nach der Verwendung des Trigger-Objekts wird das Drag-and-Drop-Objekt weiterhin verschoben, aber das Trigger-Objekt wird zum Auslösen des Drag-and-Drop verwendet (im Allgemeinen wird das Trigger-Objekt in das Drag-and-Drop-Objekt eingefügt).
Bereichsbegrenzung
Um eine Bereichsbegrenzung festzulegen, muss Limit zuerst auf true gesetzt werden. Es gibt zwei Arten von Bereichseinschränkungen, nämlich feste Bereichseinschränkungen und Containerbereichseinschränkungen, die hauptsächlich im Move-Programm festgelegt werden.
Das Prinzip besteht darin, dass, wenn der verglichene Wert den Bereich überschreitet, die einzustellenden Werte für links und oben korrigiert werden, sodass das Drag-and-Drop-Objekt innerhalb des eingestellten Bereichs bleiben kann.
Feste Bereichsbegrenzung
Die Bereichsbegrenzung des Containers besteht darin, den Drag-and-Drop-Bereich oben, unten, links und rechts anzugeben.
Die Bedeutung jedes Attributs ist:
Top (mxTop): obere Grenze; : linkes Limit;
right (mxRight): left+offsetWidth limit.
Wenn der Bereich falsch eingestellt ist, kann es dazu führen, dass der obere und untere oder linke und rechte Bereich gleichzeitig überschritten werden. Im Programm ist ein Reparaturprogramm zur Korrektur der Bereichsparameter enthalten.
Das Reparaturprogramm wird in der Programminitialisierung und im Startprogramm ausgeführt. Korrigieren Sie mxRight und mxBottom im Reparaturprogramm: this.mxRight = Math.max(this.mxRight, this.mxLeft + this. Drag.offsetWidth);this.mxBottom = Math.max(this.mxBottom, this.mxTop + this.Drag.offsetHeight);
Korrigieren Sie den Bewegungsparameter entsprechend dem Bereichsparameter:
iLeft = Math.max(Math.min(iLeft, mxRight - this.Drag.offsetWidth), mxLeft); = Math. max(Math.min(iTop, mxBottom - this.Drag.offsetHeight), mxTop);
Nehmen Sie einen größeren Wert für die obere linke Seite und einen kleineren Wert für die untere rechte Seite.
容器范围限制
容器范围限制的意思就是把范围限制在一个容器_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):
这里的参数是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:
ff:
Fügen Sie einen leeren Text in das Drag-and-Drop-Objekt ein, z. B. Dies kann behoben werden, aber dieser Fehler wurde in ff3 behoben.
Ich habe vorübergehend getestet, dass Vorgänge, die zu Fokusverlust führen können, das Wechseln von Fenstern (einschließlich Alt+Tab), Warnungen und Popups sowie andere Popup-Formulare umfassen.
Wenn der Fokus verloren geht, sollte gleichzeitig das Stop-Programm ausgeführt werden, um das Ziehen und Ablegen zu beenden. Wenn der Fokus jedoch verloren geht, kann das Mouseup-Ereignis nicht erfasst werden, d. h. _fS kann nicht ausgelöst werden.
Glücklicherweise verfügt der IE über ein onlosecapture-Ereignis, das ausgelöst wird, wenn die Erfassung fehlschlägt. Für diese Situation können Sie es wie folgt festlegen:
Dann habe ich einige Tests durchgeführt und festgestellt, dass grundsätzlich jede Situation, die losecapture auslöst, gleichzeitig window.onblur auslöst. Es scheint zu funktionieren.
Also habe ich das Programm geändert, um window.onblur anstelle von losecapture zu verwenden, aber nach dem Testen ist etwas schiefgegangen. Ich habe festgestellt, dass das Ziehen fortgesetzt werden konnte, wenn ich Alt+Tab verwendet habe, aber der Fokus hätte verloren gehen sollen zu diesem Zeitpunkt.
Sie können den folgenden Code zum Testen verwenden:
<script>window.onblur=function(){alert(1)} </script>
Das bedeutet, dass Sie den Dokumentinhalt nach setCapture nicht per Drag & Drop auswählen können. Beachten Sie, dass sich Drag & Drop hier auf die Standardaktion des Systems bezieht. Beispielsweise wird ondragstart nicht ausgelöst.
Wenn jedoch der Parameter von setCapture „false“ ist, kann das Objekt im Container das Ereignis immer noch auslösen (Einzelheiten finden Sie im Abschnitt „Mauserfassung“). Daher sollte der Parameter von „setCapture“ auf „true“ gesetzt werden oder den Standardwert beibehalten.
z. B. mit Strg+A zum Auswählen Inhalt.
ps: onkeydown-, onkeyup- und onkeypress-Ereignisse werden von der Mauserfassung nicht beeinflusst.
Und ff kann den ursprünglich ausgewählten Inhalt löschen, wenn die Maus gedrückt wird, aber durch Ziehen der Maus und Strg+A wird der Inhalt weiterhin ausgewählt.
Nachdem die Standardaktion des Systems verworfen wurde, hat diese Auswahl jedoch keinen Einfluss auf den Drag-and-Drop-Vorgang. Die Einstellung hier dient hauptsächlich dem besseren Erlebnis.
Diese Methode kann jedoch nur verhindern, dass das Drag-and-Drop-Objekt selbst ausgewählt wird. Später habe ich einen besseren Weg gefunden, die Auswahl aufzuheben, was sich nicht nur nicht auf den Auswahleffekt des Drag-and-Drop-Objekts auswirkt, sondern auch löscht das gesamte Dokument:
Um zu verhindern, dass Inhalte während des Drag-and-Drop-Vorgangs ausgewählt werden, fügen Sie sie in das Move-Programm ein. Die folgende kompatible Schreibmethode ist:
window.getSelection ? window.getSelection(). .removeAllRanges() : document.selecty();
margin
Es gibt eine andere Situation: Wenn das Drag-and-Drop-Objekt mit einem Rand festgelegt ist, wird es beim Ziehen und Ablegen falsch ausgerichtet Dropping (das Festlegen des Randes für das SimpleDrag-Drag-and-Drop-Objekt kann getestet werden).
Der Grund dafür ist, dass beim Festlegen von _x und _y im Startprogramm der Offset verwendet wird und dieser Wert den Rand einschließt, sodass dieser Rand vor der Einstellung von links und oben subtrahiert werden muss.
Wenn der Rand jedoch im Startprogramm entfernt wird, tritt beim Festlegen der Bereichsgrenze im Verschieben-Programm ein Berechnungsfehler auf.
Daher ist es am besten, den Wert im Startprogramm abzurufen:
this._marginLeft = parseInt(CurrentStyle(this.Drag).marginLeft) ||. 0;
this._marginTop = parseInt(CurrentStyle(this.Drag).marginTop) ||. 0;
wobei CurrentStyle wird verwendet, um die endgültigen Stile zu erhalten. Weitere Informationen finden Sie im Abschnitt „Endgültige Stile“ hier.
Legen Sie den Wert im Move-Programm fest:
this.Drag.style.left = iLeft - this._marginLeft + "px";
this.Drag.style.top = iTop - this._marginTop + "px";
Bitte beachten Sie, dass der Rand nach der Bereichskorrektur festgelegt werden muss, da er sonst falsch ausgerichtet wird.
[Transparenter Hintergrundfehler]
Es gibt einen transparenten Hintergrundfehler im IE (ich weiß nicht, ob es ein Fehler ist). Sie können den folgenden Code verwenden, um ihn zu testen:
Warum? Verwenden Sie dann zum Testen den folgenden Code:
Im Programm müssen Sie nur eine Hintergrundfarbe für das Drag-and-Drop-Objekt festlegen. Manchmal ist jedoch Transparenz erforderlich (z. B. bei Schnitteffekten). Was sollen wir also tun?
Das erste, was mir in den Sinn kommt, ist, eine Hintergrundfarbe hinzuzufügen und sie vollständig transparent einzustellen, aber dann sind sogar der Rand und die Objekte im Container vollständig transparent, was nicht gut ist.
with(this._Handle.appendChild(document.createElement(" div" )).style){
width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)";
Wenn es Es wird festgestellt, dass das Programm Folgendes hat: Wenn dieser Fehler auftritt, wird durch Setzen des optionalen Programmparameters „Transparent“ auf „true“ automatisch eine solche Ebene eingefügt.
Wenn Sie eine bessere Methode haben, geben Sie mir bitte einen Rat.
Das war's vorerst. Es gibt jedoch noch nicht berücksichtigte Iframes, Scrolling usw. Wir werden sie später bei Bedarf untersuchen.