Crear un juego de estacionamiento con la API HTML de arrastrar y soltar

Índice
  1. Arrastrando
  2. Goteante
  3. Reglas y Validación

Entre las muchas API de JavaScript agregadas en HTML5 se encuentra Arrastrar y soltar (lo llamaremos DnD en este artículo), que brinda soporte nativo de DnD al navegador, lo que facilitó a los desarrolladores implementar esta característica interactiva en las aplicaciones. Lo sorprendente que sucede cuando las funciones se vuelven más fáciles de implementar es que la gente comienza a hacer todo tipo de cosas tontas y poco prácticas con ellas, como la que estamos haciendo hoy: ¡un juego de estacionamiento!

Para que DnD funcione sólo se necesitan unas pocas cosas:

  • algo para arrastrar
  • Algún lugar para dejar
  • Eventos de JavaScript con el objetivo de indicarle al navegador que puede descartar

Comenzaremos creando nuestros elementos arrastrables.

Arrastrando

Ambos elementos imgy a(con el hrefconjunto de atributos) se pueden arrastrar de forma predeterminada. Si desea arrastrar un elemento diferente, deberá establecer el atributo arrastrable en true.

Comenzaremos con el HTML que configura las imágenes de nuestros cuatro vehículos: camión de bomberos, ambulancia, coche y bicicleta.

ul  li    !-- Fire Truck --    !-- codeimgcode elements don't need a codedraggablecode attribute like other elements --    img alt="fire truck" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Ftruck-clip-art-fire-truck4.png?1519011787956"/  /li  li    !-- Ambulance --    img alt="ambulance" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fambulance5.png?1519011787610"  /li  li    !-- Car --    img alt="car" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fcar-20clip-20art-1311497037_Vector_Clipart.png?1519011788408"  /li  li    !-- Bike --    img alt="bicycle" src="https://cdn.glitch.com/20f985bd-431d-4807-857b-e966e015c91b%2Fbicycle-20clip-20art-bicycle3.png?1519011787816"  /li/ul

Dado que las imágenes se pueden arrastrar de forma predeterminada, verás que al arrastrar cualquiera de ellas se crea una imagen fantasma.

Simplemente agregar un atributo arrastrable a un elemento que no sea una imagen o un enlace es realmente todo lo que se necesita para hacer que un elemento se pueda arrastrar en la mayoría de los navegadores. Para que los elementos se puedan arrastrar en todos los navegadores, es necesario definir algunos controladores de eventos. También son útiles para agregar funciones adicionales como un borde si se arrastra un elemento o un sonido si deja de arrastrarse. Para estos, necesitarán algunos controladores de eventos de arrastre, así que veamoslos.

Arrastrar eventos

Hay tres eventos relacionados con el arrastre que puedes escuchar, pero solo usaremos dos: dragstarty dragend.

  • dragstart– Se activa tan pronto como comenzamos a arrastrar. Aquí es donde podemos definir los datos de arrastre y el efecto de arrastre.
  • dragend– Se activa cuando se suelta un elemento arrastrable. Este evento generalmente se activa justo después del evento de lanzamiento de la zona de lanzamiento.

En breve cubriremos cuáles son los datos de arrastre y el efecto de arrastre.

let dragged; // Keeps track of what's being dragged - we'll use this later! function onDragStart(event) {  let target = event.target;  if (target  target.nodeName === 'IMG') { // If target is an image    dragged = target;    event.dataTransfer.setData('text', target.id);    event.dataTransfer.dropEffect = 'move';    // Make it half transparent when it's being dragged    event.target.style.opacity = .3;  }}function onDragEnd(event) {  if (event.target  event.target.nodeName === 'IMG') {    // Reset the transparency    event.target.style.opacity = ''; // Reset opacity when dragging ends     dragged = null;   }}// Adding event listenersconst vehicles = document.querySelector('.vehicles');vehicles.addEventListener('dragstart', onDragStart);vehicles.addEventListener('dragend', onDragEnd);

En este código suceden un par de cosas:

  • Estamos definiendo los datos de arrastre. Cada evento de arrastre tiene una propiedad llamada dataTransferque almacena los datos del evento. Puede utilizar el setData(type, data)método para agregar un elemento arrastrado a los datos de arrastre. Estamos almacenando el ID de la imagen arrastrada como tipo 'text'en la línea 7.
  • Estamos almacenando el elemento que se arrastra en una variable global. Sé que sé. Global es peligroso para el alcance, pero he aquí por qué lo hacemos: aunque puedes almacenar el elemento arrastrado usando setData, no puedes recuperarlo usando event.dataTransfer.getData()en todos los navegadores (excepto Firefox) porque los datos de arrastre están en modo protegido. Puedes leer más sobre esto aquí. Quería mencionar la definición de los datos de arrastre para que lo sepas.
  • Estamos configurando el dropEffecten move. La dropEffectpropiedad se utiliza para controlar la retroalimentación que recibe el usuario durante una operación de arrastrar y soltar. Por ejemplo, cambia el cursor que muestra el navegador mientras arrastra. Hay tres efectos: copiar, mover y vincular.
    • copy– Indica que los datos que se arrastran se copiarán desde su origen a la ubicación de colocación.
    • move– Indica que los datos que se están arrastrando se moverán.
    • link– Indica que se creará algún tipo de relación entre las ubicaciones de origen y destino.

Ahora tenemos vehículos que se pueden arrastrar pero no hay ningún lugar donde dejarlos:

Goteante

De forma predeterminada, cuando arrastra un elemento, solo los elementos del formulario como inputpodrán aceptarlo como una caída. Vamos a contener nuestra “zona de caída” en un sectionelemento, por lo que necesitamos agregar controladores de eventos de caída para que pueda aceptar caídas como un elemento de formulario.

Primero, dado que es un elemento vacío, necesitaremos establecer un ancho, alto y color de fondo para poder verlo en la pantalla.

Estos son los parámetros que tenemos disponibles para eventos de entrega:

  • dragenter– Se activa en el momento en que un elemento que se puede arrastrar ingresa a un área que se puede soltar. Al menos el 50% del elemento arrastrable debe estar dentro de la zona de colocación.
  • dragover– Lo mismo que dragenterpero se llama repetidamente mientras el elemento que se puede arrastrar está dentro de la zona de colocación.
  • dragleave– Se activa una vez que un elemento arrastrable se aleja de una zona de colocación.
  • drop– Se activa cuando se suelta el elemento arrastrable y el área de colocación acepta la colocación.
function onDragOver(event) {  // Prevent default to allow drop  event.preventDefault();}function onDragLeave(event) {  event.target.style.background = '';}function onDragEnter(event) {  const target = event.target;  if (target) {      event.preventDefault();      // Set the dropEffect to move      event.dataTransfer.dropEffect = 'move'      target.style.background = '#1f904e';  }}function onDrop(event) {  const target = event.target;  if ( target) {    target.style.backgroundColor = '';    event.preventDefault();    // Get the id of the target and add the moved element to the target's DOM    dragged.parentNode.removeChild(dragged);    dragged.style.opacity = '';    target.appendChild(dragged);  }}const dropZone = document.querySelector('.drop-zone');dropZone.addEventListener('drop', onDrop);dropZone.addEventListener('dragenter', onDragEnter);dropZone.addEventListener('dragleave', onDragLeave);dropZone.addEventListener('dragover', onDragOver);

Si se pregunta por qué seguimos llamando, event.preventDefault()es porque de forma predeterminada el navegador asume que cualquier destino no es un destino de colocación válido. Esto no es cierto siempre para todos los navegadores, ¡pero es mejor evitar que lamentar! Al llamar preventDefault()a los eventos dragentery dragoverdrop, se informa al navegador que el destino actual es un destino de drop válido.

¡Ahora una aplicación sencilla de arrastrar y soltar!

Es divertido, pero no tan frustrante como estacionar. Tenemos que crear algunas reglas para que eso suceda.

Reglas y Validación

Se me ocurrieron algunas reglas de estacionamiento aleatorias y te animo a que creas algunas propias. Las señales de estacionamiento generalmente indican los días y horarios en los que puede estacionar, así como qué tipos de vehículos pueden estacionar en ese momento. Cuando estábamos creando nuestros objetos arrastrables, teníamos cuatro vehículos: una ambulancia, un camión de bomberos, un coche normal y una bicicleta. Entonces, vamos a crear reglas para ellos.

  1. Estacionamiento para ambulancias únicamente: de lunes a viernes, de 9:00 pm a 3:00 am
  2. Estacionamiento exclusivo para camiones de bomberos: todo el día durante el fin de semana.
  3. Estacionamiento regular: de lunes a viernes de 3:00 a 15:00 horas.
  4. Aparcamiento de bicicletas: de lunes a viernes, de 15 a 21 horas.

Ahora, traducimos estas reglas al código. Usaremos dos bibliotecas para manejar el tiempo y los rangos: Momento y Rango-momento.

Los scripts ya están disponibles en Codepen para agregarlos a cualquier demostración nueva, pero si está desarrollando fuera de Codepen, puede copiarlos o vincularlos desde aquí:

script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"/scriptscript defer src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/3.1.1/moment-range.js"/script

Luego, creamos un objeto para almacenar todas las reglas de estacionamiento.

window['moment-range'].extendMoment(moment);// The array of weekdaysconst weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];const parkingRules =  {  ambulance: {    // The ambulance can only park on weekdays...    days: weekdays,    // ...from 9pm to 3am (the next day)    times: createRange(moment().set('hour', 21), moment().add(1, 'day').set('hour', 3))  },  'fire truck': {    // The fire truck can obnly park on Saturdays and Sundays, but all day    days: ['Saturday', 'Sunday']  },  car: {    // The car can only park on weekdays...    days: weekdays,    // ...from 3am - 3pm (the same day)    times: createRange(moment().set('hour', 3), moment().set('hour', 15))  },  bicycle: {    // The car can only park on weekdays...    days: weekdays,    // ...from 3pm - 9pm (the same day)    times: createRange(moment().set('hour', 15), moment().set('hour', 21))  }};function createRange(start, end) {  if (start  end) {    return moment.range(start, end);  }}

Cada vehículo en el parkingRulesobjeto tiene una dayspropiedad con una variedad de días que puede estacionar y una timespropiedad que es un rango de tiempo. Para obtener la hora actual usando Moment, llame al moment(). Para crear un rango usando Moment-range, pase una hora de inicio y finalización a la moment.rangefunción.

Ahora, en los controladores de eventos onDragEntery onDropque definimos anteriormente, agregamos algunas comprobaciones para asegurarnos de que un vehículo pueda estacionarse. Nuestro altatributo en la imgetiqueta almacena el tipo de vehículo, por lo que lo pasamos a un canParkmétodo que devolverá si el automóvil se puede estacionar. También agregamos señales visuales (cambio de fondo) para indicarle al usuario si un vehículo puede estacionarse o no.

function onDragEnter(event) {  const target = event.target;  if (dragged  target) {    const vehicleType = dragged.alt; // e.g bicycle, ambulance    if (canPark(vehicleType)) {      event.preventDefault();      // Set the dropEffect to move      event.dataTransfer.dropEffect = 'move';      /* Change color to green to show it can be dropped /*      target.style.background = '#1f904e';         }    else {      /* Change color to red to show it can't be dropped. Notice we       * don't call event.preventDefault() here so the browser won't       * allow a drop by default       */      target.style.backgroundColor = '#d51c00';     }  }}function onDrop(event) {  const target = event.target;  if (target) {    const data = event.dataTransfer.getData('text');    const dragged = document.getElementById(data);    const vehicleType = dragged.alt;    target.style.backgroundColor = '';    if (canPark(vehicleType)) {       event.preventDefault();       // Get the ID of the target and add the moved element to the target's DOM       dragged.style.opacity = '';       target.appendChild(dragged);    }  }}

Luego, creamos el canParkmétodo.

function getDay() {  return moment().format('dddd'); // format as 'monday' not 1}function getHours() {  return moment().hour();}function canPark(vehicle) {  /* Check the time and the type of vehicle being dragged   * to see if it can park at this time   */  if (vehicle  parkingRules[vehicle]) {    const rules = parkingRules[vehicle];    const validDays = rules.days;    const validTimes = rules.times;    const curDay = getDay();    if (validDays) {      /* If the current day is included on the parking days for the vehicle       * And if the current time is within the range       */      return validDays.includes(curDay)  (validTimes ? validTimes.contains(moment()) : true);       /* Moment.range has a contains function that checks       * to see if your range contains a moment.          https://github.com/rotaready/moment-range#contains        */    }  }  return false;}

Ahora, solo los autos que tienen permiso para estacionar pueden hacerlo. Por último, agregamos las reglas a la pantalla y le damos estilo.

Aquí está el resultado final:

Hay muchas maneras en que esto podría mejorarse:

  • ¡Genere automáticamente el HTML para la lista de reglas a partir del parkingRulesobjeto!
  • ¡Añade algunos efectos de sonido!
  • Agregue la capacidad de arrastrar vehículos al punto original sin actualizar la página.
  • Todas esas molestas variables globales.

Pero te dejaré encargarte de eso.

Si está interesado en aprender más sobre la API de DnD y algunas críticas sobre ella, aquí tiene una buena lectura:

  • Especificación WHATWG
  • Cómo trabajar con arrastrar y soltar HTML5: programación profesional en HTML5, capítulo 9, por Jen Simmons
  • Arrastrar y soltar accesible usando WAI-ARIA: consideraciones de accesibilidad de Dev.Opera
  • Arrastrar y soltar HTML5 nativo: tutorial de HTML5 Rocks
  • El desastre de arrastrar y soltar en HTML5: publicación de QuirksMode con contexto útil sobre la implementación del módulo DnD
SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion