Arrastrar y soltar es una excelente solución de interfaz. Tomar algo y arrastrarlo y soltarlo es una forma clara y sencilla de hacer muchas cosas, desde copiar y mover documentos (como en los administradores de archivos) hasta ordenar (colocar artículos en un carrito).
En el HTML moderno estándar, hay una sección sobre Arrastrar y soltar con eventos especiales como dragstart
, dragend
, etc.
Estos eventos nos permiten admitir tipos especiales de arrastrar y soltar, como manejar, arrastrar un archivo desde el administrador de archivos del sistema operativo y soltarlo en la ventana del navegador. Entonces JavaScript puede acceder al contenido de dichos archivos.
Pero los eventos de arrastre nativos también tienen limitaciones. Por ejemplo, no podemos evitar que se arrastre desde un área determinada. Además, no podemos hacer que el arrastre sea «horizontal» o «vertical» únicamente. Y hay muchas otras tareas de arrastrar y soltar que no se pueden hacer con ellas. Además, el soporte de dispositivos móviles para tales eventos es muy débil.
Así que aquí veremos cómo implementar Drag’n’Drop usando eventos del mouse.
Algoritmo Drag’n’Drop
El algoritmo básico de Drag’n’Drop se ve así:
- En
mousedown
– prepare el elemento para mover, si necesario (tal vez crear un clon de él, agregarle una clase o lo que sea). - Luego, en
mousemove
muévalo cambiandoleft/top
conposition:absolute
. - En
mouseup
– realice todas las acciones relacionadas con finalizar el drag’n ‘drop.
Estos son los conceptos básicos. Más adelante veremos otras funciones, como resaltar los elementos subyacentes actuales mientras los arrastramos.
Aquí está la implementación de arrastrar una bola:
Si ejecutamos el código, podemos notar algo extraño. Al comienzo del proceso de arrastrar y soltar, la bola se «bifurca»: comenzamos a arrastrar su «clon».
Aquí hay un ejemplo en acción:
Intente arrastrar y soltar con el mouse y verá ese comportamiento.
Esto se debe a que el navegador tiene su propio soporte para arrastrar y soltar imágenes y algunos otros elementos. Se ejecuta automáticamente y entra en conflicto con el nuestro.
Para desactivarlo:
Ahora todo estará bien.
En acción:
Otro aspecto importante – rastreamos mousemove
en document
, no en ball
. A primera vista, puede parecer que el mouse está siempre sobre la bola, y podemos poner mousemove
en él.
Pero, como recordamos, mousemove
se activa con frecuencia, pero no para cada píxel. Entonces, después de un movimiento rápido, el puntero puede saltar desde la pelota en algún lugar en el medio del documento (o incluso fuera de la ventana).
Entonces, deberíamos escuchar en document
para atraparla.
Posición correcta
En los ejemplos anteriores, la pelota siempre se mueve de modo que su centro esté debajo del puntero:
No está mal, pero hay un efecto secundario. Para iniciar la función de arrastrar y soltar, podemos mousedown
en cualquier parte de la pelota. Pero si lo «saca» de su borde, entonces la bola de repente «salta» para centrarse debajo del puntero del mouse.
Sería mejor si mantenemos el desplazamiento inicial del elemento en relación con el puntero.
Por ejemplo, si comenzamos a arrastrar por el borde de la bola, entonces el puntero debe permanecer sobre el borde mientras se arrastra.
Actualicemos nuestro algoritmo:
El código final con mejor posicionamiento:
En acción (dentro de <iframe>
) :
La diferencia se nota especialmente si arrastramos la bola por su esquina inferior derecha. En el ejemplo anterior, la pelota «salta» debajo del puntero. Ahora sigue con fluidez el puntero desde la posición actual.
Posibles objetivos de caída (droppables)
En ejemplos anteriores, la pelota podría dejarse caer «en cualquier lugar» para quedarse. En la vida real solemos tomar un elemento y colocarlo sobre otro. Por ejemplo, un «archivo» en una «carpeta» u otra cosa.
Hablando abstracto, tomamos un elemento «arrastrable» y lo soltamos en el elemento «soltable».
Nosotros necesita saber:
- dónde se colocó el elemento al final de Drag’n’Drop – para realizar la acción correspondiente,
- y, preferiblemente, conocer el droppable que está arrastrando, para resaltarlo.
La solución es algo interesante y un poco complicada, así que cubrámosla aquí.
¿Qué puede ser la primera idea? ¿Probablemente para configurar mouseover/mouseup
controladores en posibles droppables?
Pero eso no funciona.
El problema es que, mientras estamos arrastrando, el elemento arrastrable siempre está por encima de otros elementos.Y los eventos del mouse solo ocurren en el elemento superior, no en los que están debajo.
Por ejemplo, debajo hay dos elementos <div>
, uno rojo sobre el azul uno (cubre completamente). No hay forma de capturar un evento en el azul, porque el rojo está en la parte superior:
Lo mismo con un elemento que se puede arrastrar. La pelota siempre está encima de otros elementos, por lo que ocurren eventos en ella. Independientemente de los controladores que establezcamos en elementos inferiores, no funcionarán.
Es por eso que la idea inicial de poner controladores en posibles droppables no funciona en la práctica. No se ejecutarán.
Entonces, ¿qué hacer?
Existe un método llamado document.elementFromPoint(clientX, clientY)
. Devuelve el elemento más anidado en las coordenadas relativas a la ventana dadas (o null
si las coordenadas dadas están fuera de la ventana).
Podemos usarlo en cualquiera de nuestros controladores de eventos del mouse para detectar el potencial droppable debajo del puntero, como este:
Tenga en cuenta: necesitamos ocultar la bola antes de la llamada (*)
. De lo contrario, normalmente tendremos una bola en estas coordenadas, ya que es el elemento superior debajo del puntero: elemBelow=ball
. Así que lo ocultamos y lo mostramos de nuevo inmediatamente.
Podemos usar ese código para verificar qué elemento estamos «volando» en cualquier momento. Y manejar la caída cuando suceda.
Un código extendido de onMouseMove
para encontrar elementos «soltables»:
En el siguiente ejemplo, cuando la pelota se arrastra sobre la portería de fútbol, la portería se resalta.
Ahora tenemos el «drop target» actual, sobre el que estamos volando, en la variable currentDroppable
durante todo el proceso y podemos usarlo para resaltar o cualquier otra cosa.
Resumen
Consideramos un algoritmo básico de Drag’n’Drop.
Los componentes clave:
- Flujo de eventos:
ball.mousedown
→document.mousemove
→ball.mouseup
(no olvide cancelar laondragstart
). - Al inicio del arrastre, recuerde el desplazamiento inicial del puntero en relación con el elemento:
shiftX/shiftY
y mantenerlo durante el arrastre. - Detectar elementos que se pueden soltar y er el puntero usando
document.elementFromPoint
.
Podemos poner mucho sobre esta base.
- En
mouseup
podemos finalizar intelectualmente la caída: cambiar datos, mover elementos. - Podemos resaltar los elementos sobre los que estamos volando.
- Nosotros puede limitar el arrastre por un área o dirección determinada.
- Podemos usar la delegación de eventos para
mousedown/up
. Un controlador de eventos de área grande que verificaevent.target
puede administrar Drag’n’Drop para cientos de elementos. - Y así sucesivamente.
Hay marcos que construyen arquitectura sobre él: DragZone
, Droppable
, Draggable
y otras clases. La mayoría de ellos hacen cosas similares a las descritas anteriormente, por lo que debería ser fácil de entender ahora. O cree el suyo propio, ya que puede ver que es bastante fácil de hacer, a veces más fácil que adaptar una solución de terceros.