Drag’n’Drop este o soluție excelentă de interfață. A lua ceva, a-l trage și a-l plasa este o modalitate clară și simplă de a face multe lucruri, de la copierea și mutarea documentelor (ca în managerii de fișiere) până la comandare (aruncarea articolelor într-un coș). standard există o secțiune despre Drag and Drop cu evenimente speciale precum dragstart
, dragend
și așa mai departe.
Aceste evenimente ne permit să acceptăm tipuri speciale de drag’n’drop, cum ar fi gestionarea glisării unui fișier din managerul de fișiere al sistemului de operare și plasarea acestuia în fereastra browserului. Apoi, JavaScript poate accesa conținutul acestor fișiere.
Dar evenimentele de drag nativ au și ele limitări. De exemplu, nu putem împiedica glisarea dintr-o anumită zonă. De asemenea, nu putem face doar glisarea „orizontală” sau „verticală”. Și există multe alte sarcini drag’n’drop care nu pot fi realizate folosindu-le. De asemenea, suportul dispozitivelor mobile pentru astfel de evenimente este foarte slab.
Deci, aici vom vedea cum să implementăm Drag’n’Drop folosind evenimente de mouse.
Algoritmul Drag’n’Drop
Algoritmul de bază Drag’n’Drop arată astfel:
- Activat
mousedown
– pregătiți elementul pentru mișcare, dacă necesar (poate creați o clonă a acestuia, adăugați o clasă sau orice altceva). - Apoi, pe
mousemove
mutați-l schimbândleft/top
cuposition:absolute
. - La
mouseup
– efectuați toate acțiunile legate de finalizarea glisării ‘drop.
Acestea sunt elementele de bază. Mai târziu vom vedea cum să facem alte funcții, cum ar fi evidențierea elementelor actuale care stau la baza lor în timp ce le tragem peste ele.
Iată implementarea glisării unei mingi:
Dacă rulăm codul, putem observa ceva ciudat. La începutul glisării, mingea „se bifurcă”: începem să-i tragem „clona”.
Iată un exemplu în acțiune:
Încercați să glisați cu mouse-ul și veți vedea un astfel de comportament.
Acest lucru se datorează faptului că browserul are propriul suport de drag-drop pentru imagini și alte câteva elemente. Se execută automat și intră în conflict cu a noastră.
Pentru a o dezactiva:
Acum totul va fi în regulă.
În acțiune:
Un alt aspect important – urmărim mousemove
pe document
, nu pe ball
. Din prima vedere poate părea că mouse-ul este întotdeauna deasupra mingii și putem pune mousemove
pe el.
Dar, după cum ne amintim, mousemove
se declanșează des, dar nu pentru fiecare pixel. Deci, după o mișcare rapidă, indicatorul poate sări de la minge undeva în mijlocul documentului (sau chiar în afara ferestrei).
Deci ar trebui să ascultăm pe document
pentru a o prinde.
Poziționarea corectă
În exemplele de mai sus, mingea este întotdeauna mutată astfel încât centrul ei să fie sub pointer:
Nu este rău, dar există un efect secundar. Pentru a iniția drag’n’drop, putem mousedown
oriunde pe minge. Dar dacă „o ia” de la margine, atunci mingea „sare” brusc pentru a deveni centrată sub indicatorul mouse-ului.
Ar fi mai bine dacă păstrăm schimbarea inițială a elementului față de indicator.
De exemplu, dacă începem să trageți de marginea mingii, atunci indicatorul ar trebui să rămână peste margine în timp ce trageți.
Să ne actualizăm algoritmul:
Codul final cu o poziționare mai bună:
În acțiune (în interiorul <iframe>
) :
Diferența se observă mai ales dacă tragem mingea de colțul din dreapta jos. În exemplul anterior, mingea „sare” sub pointer. Acum urmărește fluent indicatorul din poziția actuală.
Țintele potențiale de scădere (droppables)
În exemplele anterioare, mingea ar putea să fie lăsat doar „oriunde” pentru a rămâne. În viața reală luăm de obicei un element și îl lăsăm pe altul. De exemplu, un „fișier” într-un „folder” sau altceva.
Vorbind abstract, luăm un element „glisabil” și îl lăsăm pe elementul „droppable”.
Noi trebuie să știți:
- unde a fost scăpat elementul la sfârșitul Drag’n’Drop – pentru a efectua acțiunea corespunzătoare,
- și, de preferință, să știm ce este Trageți peste, pentru a o evidenția.
Soluția este cam interesantă și puțin cam dificilă, așa că hai să o acoperim aici.
Ce poate fi prima idee? Probabil pentru a seta mouseover/mouseup
handlers pe potențiale droppables?
Dar asta nu funcționează.
Problema este că, în timp ce suntem glisând, elementul glisabil este întotdeauna deasupra altor elemente.Și evenimentele mouse-ului se întâmplă numai pe elementul de sus, nu pe cele de sub acesta.
De exemplu, mai jos sunt două elemente <div>
, roșu deasupra albastru una (acoperă complet). Nu există nicio modalitate de a prinde un eveniment pe cel albastru, deoarece roșu este deasupra:
La fel cu un element glisabil. Mingea este întotdeauna deasupra altor elemente, așa că se întâmplă evenimente pe ea. Indiferent de manevrele pe care le-am setat pe elemente inferioare, acestea nu vor funcționa.
De aceea ideea inițială de a pune handlerele pe potențiale droppables nu funcționează în practică. Nu vor rula.
Deci, ce să faci?
Există o metodă numită document.elementFromPoint(clientX, clientY)
. Returnează cel mai imbricat element pe coordonatele date relative la fereastră (sau null
dacă coordonatele date sunt în afara ferestrei).
Îl putem folosi în gestionarii noștri de evenimente de la mouse pentru a detecta potențialul care poate fi scăpat sub pointer, astfel:
Rețineți: trebuie să ascundem mingea înainte de apel (*)
. În caz contrar, vom avea de obicei o minge pe aceste coordonate, deoarece este elementul de sus sub indicator: elemBelow=ball
. Așa că o ascundem și o afișăm imediat din nou.
Putem folosi codul respectiv pentru a verifica ce element „zburăm” în orice moment. Și gestionăm picătura când se întâmplă.
Un cod extins de onMouseMove
pentru a găsi elemente „dropable”:
În exemplul de mai jos, când mingea este trasă peste poarta de fotbal, obiectivul este evidențiat.
Acum avem actualul „drop target”, peste care zburăm, în variabila currentDroppable
pe parcursul întregului proces și îl putem folosi pentru a evidenția sau orice alte lucruri.
Rezumat
Am considerat un algoritm de bază Drag’n’Drop.
Componentele cheie:
- Flux de evenimente:
ball.mousedown
→document.mousemove
→ball.mouseup
(nu uitați să anulați nativulondragstart
). - La pornire prin tragere – amintiți-vă de schimbarea inițială a indicatorului în raport cu elementul:
shiftX/shiftY
și păstrați-l în timpul glisării. - Detectați elementele care pot fi plasate und er pointer folosind
document.elementFromPoint
.
Putem pune multe pe această bază.
- Pe
mouseup
putem finaliza intelectual picătura: schimbăm date, mutăm elemente. - Putem evidenția elementele pe care le zburăm.
- Noi poate limita glisarea după o anumită zonă sau direcție.
- Putem folosi delegarea evenimentelor pentru
mousedown/up
. Un handler de evenimente cu suprafață mare care verificăevent.target
poate gestiona Drag’n’Drop pentru sute de elemente. - Și așa mai departe.
Există cadre care construiesc arhitectură peste ea: DragZone
, Droppable
, Draggable
și alte clase. Majoritatea fac lucruri similare cu cele descrise mai sus, deci ar trebui să fie ușor de înțeles acum. Sau rulați-vă propriul dvs., deoarece puteți vedea că este suficient de ușor de făcut, uneori mai ușor decât adaptarea unei soluții terță parte.