Drag’n’Drop is een geweldige interface-oplossing. Iets pakken en slepen en neerzetten is een duidelijke en eenvoudige manier om veel dingen te doen, van het kopiëren en verplaatsen van documenten (zoals in bestandsbeheerders) tot het bestellen (items in een winkelwagentje plaatsen).
In de moderne HTML standaard is er een sectie over slepen en neerzetten met speciale gebeurtenissen zoals dragstart
, dragend
, enzovoort.
Deze gebeurtenissen stellen ons in staat om speciale soorten drag’n’drop te ondersteunen, zoals het afhandelen van het slepen van een bestand vanuit OS File Manager en het neerzetten in het browservenster. Dan heeft JavaScript toegang tot de inhoud van dergelijke bestanden.
Maar native Drag Events hebben ook beperkingen. We kunnen bijvoorbeeld niet voorkomen dat u vanuit een bepaald gebied sleept. We kunnen het slepen ook niet alleen “horizontaal” of “verticaal” maken. En er zijn veel andere sleeptaken die niet met deze taken kunnen worden uitgevoerd. Bovendien is de ondersteuning van mobiele apparaten voor dergelijke gebeurtenissen erg zwak.
Hier zullen we zien hoe we Drag’n’Drop kunnen implementeren met muisgebeurtenissen.
Drag’n’Drop-algoritme
Het standaard Drag’n’Drop-algoritme ziet er als volgt uit:
- Op
mousedown
– bereid het element voor op verplaatsing, als nodig (maak er misschien een kloon van, voeg er een klasse aan toe of wat dan ook). - Verplaats het vervolgens op
mousemove
door metposition:absolute
. - Op
mouseup
– voer alle acties uit die verband houden met het voltooien van het slepen ‘drop.
Dit zijn de basisprincipes. Later zullen we zien hoe andere functies kunnen worden gebruikt, zoals het markeren van huidige onderliggende elementen terwijl we eroverheen slepen.
Hier is de implementatie van het slepen van een bal:
Als we de code uitvoeren, we kunnen iets vreemds opmerken. Aan het begin van de drag’n’drop, de bal “vorken”: we beginnen de “kloon” te slepen.
Hier is een voorbeeld in actie:
Probeer met de muis te slepen en neerzetten en je zult dergelijk gedrag zien.
Dat komt omdat de browser zijn eigen ondersteuning voor slepen en neerzetten heeft voor afbeeldingen en enkele andere elementen. Het wordt automatisch uitgevoerd en is in strijd met de onze.
Om het uit te schakelen:
Nu komt alles goed.
In actie:
Nog een belangrijk aspect – we volgen mousemove
op document
, niet op ball
. Op het eerste gezicht lijkt het misschien dat de muis altijd boven de bal is, en we kunnen er mousemove
op plaatsen.
Maar zoals we ons herinneren, mousemove
triggers vaak, maar niet voor elke pixel. Dus na een snelle beweging kan de aanwijzer van de bal ergens in het midden van het document (of zelfs buiten het venster) springen.
Dus we moeten luisteren op document
om hem te vangen.
Correcte positionering
In de bovenstaande voorbeelden wordt de bal altijd zo bewogen, dat het midden onder de aanwijzer staat:
Niet slecht, maar er is een neveneffect. Om het slepen te starten, kunnen we mousedown
overal op de bal. Maar als het van zijn rand “wordt genomen”, dan “springt” de bal plotseling om gecentreerd te worden onder de muisaanwijzer.
Het zou beter zijn als we de oorspronkelijke verschuiving van het element ten opzichte van de aanwijzer behouden.
Als we bijvoorbeeld aan de rand van de bal beginnen te slepen, moet de aanwijzer tijdens het slepen boven de rand blijven.
Laten we ons algoritme updaten:
De laatste code met een betere positionering:
In actie (binnen <iframe>
) :
Het verschil is vooral merkbaar als we de bal aan de rechter benedenhoek slepen. In het vorige voorbeeld “springt” de bal onder de aanwijzer. Nu volgt hij vloeiend de aanwijzer vanaf de huidige positie.
Potentiële drop-doelen (droppables)
In eerdere voorbeelden kon de bal gewoon “overal” worden neergezet om te blijven. In het echte leven nemen we meestal het ene element en laten het op een ander vallen. Bijvoorbeeld, een “bestand” in een “map” of iets anders.
Als we abstract spreken, nemen we een “versleepbaar” element en zetten het neer op een “verwijderbaar” element.
We moet weten:
- waar het element is neergezet aan het einde van Drag’n’Drop – om de bijbehorende actie uit te voeren,
- en bij voorkeur de verwijderbare we slepen over om het te markeren.
De oplossing is best interessant en een klein beetje lastig, dus laten we het hier bespreken.
Wat zou het kunnen zijn het eerste idee? Waarschijnlijk om mouseover/mouseup
handlers in te stellen op mogelijke droppables?
Maar dat werkt niet.
Het probleem is dat, terwijl we slepen, het versleepbare element staat altijd boven andere elementen.En muisgebeurtenissen vinden alleen plaats op het bovenste element, niet op de elementen eronder.
Hieronder staan bijvoorbeeld twee <div>
-elementen, de rode bovenop de blauwe één (dekt volledig). Er is geen manier om een evenement op de blauwe te vangen, omdat het rood bovenaan staat:
Hetzelfde met een versleepbaar element. De bal ligt altijd bovenop andere elementen, dus er gebeuren gebeurtenissen op. Welke handlers we ook instellen op lagere elementen, ze zullen niet werken.
Daarom werkt het oorspronkelijke idee om handlers op potentiële droppables te plaatsen in de praktijk niet. Ze worden niet uitgevoerd.
Dus, wat te doen?
Er is een methode genaamd document.elementFromPoint(clientX, clientY)
. Het retourneert het meest geneste element op gegeven venster-relatieve coördinaten (of null
als bepaalde coördinaten buiten het venster zijn).
We kunnen het gebruiken in elk van onze muisgebeurtenis-handlers om het potentieel verwijderbare onder de aanwijzer te detecteren, zoals dit:
Let op: we moeten de bal verbergen voor de aanroep (*)
. Anders hebben we meestal een bal op deze coördinaten, omdat dit het bovenste element is onder de aanwijzer: elemBelow=ball
. Dus we verbergen het en laten het onmiddellijk weer zien.
We kunnen die code gebruiken om op elk moment te controleren over welk element we “vliegen”. En we kunnen het laten vallen wanneer het gebeurt.
Een uitgebreide code van onMouseMove
om “verwijderbare” elementen te vinden:
In het voorbeeld hieronder, wanneer de bal over het voetbaldoel wordt gesleept, wordt het doel gemarkeerd.
Nu hebben we het huidige “drop target”, waarover we vliegen, in de variabele currentDroppable
gedurende het hele proces en kunnen we het gebruiken om andere dingen.
Samenvatting
We hebben een standaard Drag’n’Drop-algoritme overwogen.
De belangrijkste componenten:
- Gebeurtenissenstroom:
ball.mousedown
→document.mousemove
→ball.mouseup
(vergeet niet nativeondragstart
). - Bij het begin van het slepen – onthoud de aanvankelijke verschuiving van de aanwijzer ten opzichte van het element:
shiftX/shiftY
en bewaar het tijdens het slepen. - Detecteer verwijderbare elementen en er de aanwijzer met
document.elementFromPoint
.
We kunnen veel op dit fundament leggen.
- Op
mouseup
we kunnen het neerzetten intellectueel afronden: gegevens wijzigen, elementen verplaatsen. - We kunnen de elementen markeren waarover we vliegen.
- We kan het slepen beperken tot een bepaald gebied of richting.
- We kunnen gebeurtenisdelegatie gebruiken voor
mousedown/up
. Een event-handler voor een groot gebied dieevent.target
controleert, kan Drag’n’Drop beheren voor honderden elementen. - Enzovoort.
Er zijn frameworks die er architectuur overheen bouwen: DragZone
, Droppable
, Draggable
en andere klassen. De meesten van hen doen hetzelfde als wat hierboven is beschreven, dus het zou nu gemakkelijk moeten zijn om ze te begrijpen. Of rol het zelf, zoals u kunt zien, dat is eenvoudig genoeg om te doen, soms gemakkelijker dan het aanpassen van een oplossing van derden.