Krypton Solid

Eventos de entrada del navegador: ¿podemos hacerlo mejor que el clic?

Eventos de entrada del navegador: ¿podemos hacerlo mejor que el clic?

Responder a la entrada del usuario es posiblemente el núcleo de lo que hacemos como desarrolladores de interfaces. Para crear productos web receptivos, es fundamental comprender cómo funcionan en conjunto las acciones del tacto, el mouse, el puntero y el teclado y el navegador. Es probable que haya experimentado el retraso de 300 milisegundos en los navegadores móviles o haya luchado con touchmove versus scrolling. En este artículo presentaremos la cascada de eventos y usaremos este conocimiento para implementar una demostración de un evento tap que admite los muchos métodos de entrada sin interrumpir los navegadores proxy como Opera Mini.

Responder a la entrada del usuario es posiblemente el núcleo de lo que hacemos como desarrolladores de interfaces. Para crear productos web receptivos, es fundamental comprender cómo funcionan en conjunto las acciones del tacto, el mouse, el puntero y el teclado y el navegador. Probablemente haya experimentado el Retraso de 300 milisegundos en navegadores móviles o luchó con touchmove versus desplazamiento.

En este artículo, presentaremos la cascada de eventos y usaremos este conocimiento para implementar una demostración de un evento tap que admite los muchos métodos de entrada sin interrumpir los navegadores proxy como Opera Mini.

Descripción general

Actualmente, se utilizan tres métodos de entrada principales para interactuar con la web: cursores digitales (mouse), táctiles (toque directo o lápiz óptico) y teclados. Tenemos acceso a estos en JavaScript a través de toque eventos, eventos del mouse, eventos de puntero y eventos de teclado. En este artículo nos ocupamos principalmente de las interacciones táctiles y basadas en el mouse, aunque algunos eventos tienen interacciones estándar basadas en el teclado, como el click y submit eventos.

Es muy probable que ya haya implementado controladores de eventos para eventos táctiles y de mouse. Hubo un momento en nuestro pase no muy lejano en el que el método recomendado era algo parecido a esto:


/** DO NOT EVER DO THIS! */
$('a', ('ontouchstart' in window) ? 'touchend' : 'click', handler);

Microsoft ha liderado la tarea de crear un mejor modelo de eventos orientado al futuro con la especificación «Pointer Events». Los eventos de puntero son un mecanismo de entrada abstracto que ahora es una recomendación del W3C. Los eventos de puntero brindan al agente de usuario (UA) flexibilidad para albergar numerosos mecanismos de entrada en un sistema de eventos. Mouse, touch y stylus son ejemplos que vienen a la mente fácilmente hoy en día, aunque las implementaciones se extienden a Myo o Anillo son imaginables. Si bien los desarrolladores web parecen estar realmente entusiasmados con esto, no todos los ingenieros de navegadores han sentido lo mismo. Es decir, Apple y Google han decidido no implementar eventos de puntero en este momento.

La decisión de Google no es necesariamente definitiva, pero no se está realizando ningún trabajo activo sobre los eventos de puntero. Nuestra entrada y uso de eventos de puntero a través de polyfills y soluciones alternativas serán parte de la ecuación que eventualmente podría inclinar la balanza hacia el otro lado. Apple hizo su declaración contra los eventos punteros en 2012, y no tengo conocimiento de ninguna respuesta pública por parte de los ingenieros de Safari.

La cascada de eventos

Cuando un usuario toca un elemento en un dispositivo móvil, el navegador activa una gran cantidad de eventos. Esta acción normalmente desencadena una serie de eventos como los siguientes: touchstarttouchendmouseovermousemovemousedownmouseupclick.

Esto se debe a la compatibilidad con versiones anteriores de la web. Los eventos de puntero adoptan un enfoque alternativo, activando eventos de compatibilidad en línea: mousemovepointerovermouseoverpointerdownmousedowngotpointercapturepointerupmouseuplostpointercapturepointeroutmouseoutfocusclick.

La especificación de eventos permite que los AU difieran en su implementación de eventos de compatibilidad. Patrick Lauke y Peter-Paul Koch mantienen un extenso material de referencia sobre este tema, que está vinculado en la sección de recursos al final de este artículo.

Los siguientes gráficos muestran la cascada de eventos para las siguientes acciones:

  1. un toque inicial en un elemento,
  2. un segundo toque en un elemento,
  3. tocando el elemento.

Tenga en cuenta: esta pila de eventos ignora intencionalmente dónde focus y blur los eventos encajan en esta pila.

La cascada de eventos en los dispositivos iOS para tocar un elemento dos veces y luego alejarlo. (Imagen: Stephen Davis) (Ver versión grande)
La cascada de eventos en la mayoría de los dispositivos Android 4.4 para tocar un elemento dos veces y luego alejarlo
La cascada de eventos en la mayoría de los dispositivos Android 4.4 para tocar un elemento dos veces y luego alejarlo. (Imagen: Stephen Davis) (Ver versión grande)
La cascada de eventos en IE 11 (antes de que se implementaran los eventos táctiles de compatibilidad) para tocar un elemento dos veces y luego alejarlo.
La cascada de eventos en Internet Explorer 11 (antes de que se implementaran los eventos táctiles de compatibilidad) para tocar un elemento dos veces y luego tocarlo. (Imagen: Stephen Davis) (Ver versión grande)

Aplicar la cascada de eventos

La mayoría de los sitios web creados hoy para la web de escritorio «simplemente funcionan» gracias a los esfuerzos de los ingenieros de navegadores. A pesar de que la cascada parece retorcida, el enfoque conservador de construir eventos de ratón como lo hemos hecho anteriormente en general funcionará.

Por supuesto, hay una trampa. El infame retraso de 300 milisegundos es el más famoso, pero la interacción entre el desplazamiento, touchmove y pointermove Los eventos y la pintura del navegador son problemas adicionales. Evitar el retraso de 300 milisegundos es fácil si:

  • optimizamos solo para Chrome moderno para Android y escritorio, que utilizan heurísticas como <meta name="viewport" content="width=device-width"> para deshabilitar el retraso;
  • optimizamos solo para iOS, y el usuario hace una pulsación clara, pero no una pulsación rápida ni una pulsación larga, solo una pulsación buena, normal y clara de un elemento (oh, también depende de si está en un UIWebView o en un WKWebView – leer Problema de FastClick sobre el tema por un buen llanto).

Si nuestro objetivo es crear productos web que compitan con las plataformas nativas en cuanto a experiencia de usuario y pulido, entonces debemos reducir la latencia de la respuesta de interacción. Para lograr esto, necesitamos construir sobre los eventos primitivos (down, move y up) y creando nuestros propios eventos compuestos (click, double-click). Por supuesto, todavía necesitamos incluir controladores de reserva para los eventos nativos para un amplio soporte y accesibilidad.

Hacer esto requiere no poca cantidad de código o conocimiento. Para evitar la demora de 300 milisegundos (o cualquier duración) en los navegadores, debemos manejar el ciclo de vida completo de la interacción nosotros mismos. Para una dada {type}down evento, necesitaremos vincular todos los eventos que serán necesarios para completar esa acción. Cuando se complete la interacción, tendremos que limpiar después de nosotros mismos desvinculándonos de todo excepto el evento inicial.

Usted, el desarrollador del sitio web, es el único que sabe si la página debe hacer zoom o si debe esperar otro evento de doble toque. Si, y solo si, necesita que la devolución de llamada se demore, debe permitir un retraso para la acción prevista.

En el siguiente enlace, encontrará una pequeña demostración de tap sin dependencia para ilustrar el esfuerzo requerido para crear un evento tap de múltiples entradas y baja latencia. Polymer-gestures es una implementación lista para producción del grifo y otros eventos. A pesar de su nombre, no está vinculado a la biblioteca de polímeros de ninguna manera y se puede usar fácilmente de forma aislada.

Para ser claros, implementar esto desde cero es una mala idea. Lo siguiente es solo para fines educativos y no debe usarse en producción. Existen soluciones listas para la producción, como FastClick, gestos-poliméricos y Hammer.js.

Los bits importantes

Vincular sus controladores de eventos iniciales es donde comienza todo. El siguiente patrón se considera la forma a prueba de balas de manejar la entrada de múltiples dispositivos.


/**
 * If there are pointer events, let the platform handle the input 
 * mechanism abstraction. If not, then it’s on you to handle 
 * between mouse and touch events.
 */

if (hasPointer) {
  tappable.addEventListener(POINTER_DOWN, tapStart, false);
  clickable.addEventListener(POINTER_DOWN, clickStart, false);
}

else {
  tappable.addEventListener('mousedown', tapStart, false);
  clickable.addEventListener('mousedown', clickStart, false);

  if (hasTouch) {
    tappable.addEventListener('touchstart', tapStart, false);
    clickable.addEventListener('touchstart', clickStart, false);
  }
}

clickable.addEventListener('click', clickEnd, false);

La vinculación de controladores de eventos táctiles podría comprometer el rendimiento de la representación, incluso si no hacen nada. Para disminuir este impacto, a menudo se recomienda vincular eventos de seguimiento en el controlador del evento inicial. No olvide limpiar después de usted mismo y desvincular los eventos de seguimiento en sus controladores de acciones completadas.


/**
 * On tapStart we want to bind our move and end events to detect 
 * whether this is a “tap” action.
 * @param {Event} event the browser event object
 */

function tapStart(event) {
  // bind tracking events. “bindEventsFor” is a helper that automatically 
  // binds the appropriate pointer, touch or mouse events based on our 
  // current event type. Additionally, it saves the event target to give 
  // us similar behavior to pointer events’ “setPointerCapture” method.

  bindEventsFor(event.type, event.target);
  if (typeof event.setPointerCapture === 'function') {
    event.currentTarget.setPointerCapture(event.pointerId);
  }

  // prevent the cascade
  event.preventDefault();

  // start our profiler to track time between events
  set(event, 'tapStart', Date.now());
}

/**
 * tapEnd. Our work here is done. Let’s clean up our tracking events.
 * @param {Element} target the html element
 * @param {Event} event the browser event object
 */

function tapEnd(target, event) {
  unbindEventsFor(event.type, target);
  var _id = idFor(event);
  log('Tap', diff(get(_id, 'tapStart'), Date.now()));
  setTimeout(function() {
    delete events[_id];
  });
}

El resto del código debería ser bastante autoexplicativo. En verdad, es mucha contabilidad. La implementación de gestos personalizados requiere que trabaje en estrecha colaboración con el sistema de eventos del navegador. Para ahorrarse el dolor y la angustia, no haga esto a la carta en toda su base de código; más bien, construya o use una abstracción fuerte, como Hammer.js, la Eventos de puntero jQuery polyfill o polyfill-gestures.

Conclusión

Ciertos eventos que solían ser muy claros ahora están llenos de ambigüedad. La click El evento solía significar una cosa y solo una cosa, pero las pantallas táctiles lo han complicado al necesitar discernir si la acción es un doble clic, un desplazamiento, un evento o algún otro gesto a nivel del sistema operativo.

La buena noticia es que ahora entendemos mucho mejor la cascada de eventos y la interacción entre la acción de un usuario y la respuesta del navegador. Al comprender las primitivas en funcionamiento, podemos tomar mejores decisiones en nuestros proyectos, para nuestros usuarios y para el futuro de la web.

¿Con qué problemas inesperados se ha encontrado al crear sitios web multidispositivo? ¿Qué enfoques ha tomado para resolver los numerosos modelos de interacción que tenemos en la web?

Recursos adicionales

Deja un comentario