Señalar el camino a seguir

Sérgio Gomes

Señalar elementos en la Web solía ser sencillo. Tenías un mouse, lo movías, a veces presionabas botones y eso era todo. Todo lo que no era un mouse se emulaba como uno, y los desarrolladores sabían exactamente con qué contar.

Sin embargo, simple no significa necesariamente bueno. Con el tiempo, se hizo cada vez más importante que no todo fuera (o pretendiera ser) un mouse: podías tener plumas sensibles a la presión y a la inclinación para obtener una increíble libertad creativa; podías usar los dedos, por lo que solo necesitabas el dispositivo y la mano. Y, ¿por qué no usar más de un dedo mientras lo haces?

Hace tiempo que tenemos eventos táctiles para ayudarnos con eso, pero son una API completamente independiente específicamente para la función táctil, lo que te obliga a codificar dos modelos de eventos separados si quieres admitir el mouse y la función táctil. Chrome 55 se envía con un estándar más reciente que unifica ambos modelos: eventos del puntero.

Un modelo de evento único

Los eventos del puntero unifican el modelo de entrada del puntero para el navegador, uniendo la función táctil, los lápices y los mouse en un solo conjunto de eventos. Por ejemplo:

document.addEventListener('pointermove',
    ev
=> console.log('The pointer moved.'));
foo
.addEventListener('pointerover',
    ev
=> console.log('The pointer is now over foo.'));

Esta es una lista de todos los eventos disponibles, que deberían resultarte bastante familiares si conoces los eventos del mouse:

pointerover El puntero entró en el cuadro de límite del elemento. Esto sucede de inmediato en los dispositivos que admiten el desplazamiento del mouse, o antes de un evento pointerdown en los dispositivos que no lo admiten.
pointerenter Es similar a pointerover, pero no genera burbujas y controla a los descendientes de manera diferente. Detalles sobre las especificaciones.
pointerdown El puntero entró en el estado de botón activo, ya sea que se presione un botón o se establezca el contacto, según la semántica del dispositivo de entrada.
pointermove El puntero cambió de posición.
pointerup El puntero salió del estado de botón activo.
pointercancel Ocurrió algo que significa que es poco probable que el puntero emita más eventos. Esto significa que debes cancelar las acciones en curso y volver a un estado de entrada neutral.
pointerout El puntero salió del cuadro de límite del elemento o la pantalla. También después de un pointerup, si el dispositivo no admite el desplazamiento del mouse.
pointerleave Es similar a pointerout, pero no genera burbujas y controla a los descendientes de manera diferente. Detalles sobre las especificaciones.
gotpointercapture El elemento recibió la captura del puntero.
lostpointercapture Se liberó el puntero que se estaba capturando.

Diferentes tipos de entrada

Por lo general, los eventos del puntero te permiten escribir código de forma independiente de la entrada, sin necesidad de registrar controladores de eventos separados para diferentes dispositivos de entrada. Por supuesto, deberás tener en cuenta las diferencias entre los tipos de entrada, por ejemplo, si se aplica el concepto de desplazamiento del mouse. Si quieres diferenciar los diferentes tipos de dispositivos de entrada, por ejemplo, para proporcionar código o funcionalidad separados para diferentes entradas, puedes hacerlo desde los mismos controladores de eventos con la propiedad pointerType de la interfaz PointerEvent. Por ejemplo, si codificaras un panel lateral de navegación, podrías tener la siguiente lógica en tu evento pointermove:

switch(ev.pointerType) {
   
case 'mouse':
   
// Do nothing.
   
break;
   
case 'touch':
   
// Allow drag gesture.
   
break;
   
case 'pen':
   
// Also allow drag gesture.
   
break;
   
default:
   
// Getting an empty string means the browser doesn't know
   
// what device type it is. Let's assume mouse and do nothing.
   
break;
}

Acciones predeterminadas

En los navegadores compatibles con la función táctil, se usan ciertos gestos para desplazar la página, acercarla o actualizarla. En el caso de los eventos táctiles, seguirás recibiendo eventos mientras se realizan estas acciones predeterminadas; por ejemplo, touchmove se seguirá activando mientras el usuario se desplaza.

Con los eventos del puntero, cada vez que se active una acción predeterminada, como el desplazamiento o el zoom, recibirás un evento pointercancel para informarte que el navegador tomó el control del puntero. Por ejemplo:

document.addEventListener('pointercancel',
    ev
=> console.log('Go home, the browser is in charge now.'));

Velocidad integrada: Este modelo permite un mejor rendimiento de forma predeterminada, en comparación con los eventos táctiles, en los que deberías usar objetos de escucha de eventos pasivos para lograr el mismo nivel de capacidad de respuesta.

Puedes evitar que el navegador tome el control con la propiedad CSS touch-action. Si lo estableces en none en un elemento, se inhabilitarán todas las acciones definidas por el navegador que se inicien en ese elemento. Sin embargo, hay varios otros valores para un control más detallado, como pan-x, que permiten que el navegador reaccione al movimiento en el eje x, pero no en el eje y. Chrome 55 admite los siguientes valores:

auto Es el valor predeterminado. El navegador puede realizar cualquier acción predeterminada.
none El navegador no puede realizar ninguna acción predeterminada.
pan-x El navegador solo puede realizar la acción predeterminada de desplazamiento horizontal.
pan-y El navegador solo puede realizar la acción predeterminada de desplazamiento vertical.
pan-left El navegador solo puede realizar la acción predeterminada de desplazamiento horizontal y desplazar la página hacia la izquierda.
pan-right El navegador solo puede realizar la acción predeterminada de desplazamiento horizontal y desplazar la página hacia la derecha.
pan-up El navegador solo puede realizar la acción predeterminada de desplazamiento vertical y desplazar la página hacia arriba.
pan-down El navegador solo puede realizar la acción predeterminada de desplazamiento vertical y desplazar la página hacia abajo.
manipulation El navegador solo puede realizar acciones de desplazamiento y zoom.

Captura del puntero

¿Alguna vez pasaste una hora frustrante depurando un evento mouseup dañado hasta que te diste cuenta de que se debía a que el usuario soltaba el botón fuera de tu objetivo de clic? ¿No? De acuerdo, tal vez sea solo yo.

Sin embargo, hasta ahora no había una forma realmente buena de abordar este problema. Por supuesto, podrías configurar el controlador mouseup en el documento y guardar un estado en tu aplicación para hacer un seguimiento de todo. Sin embargo, esa no es la solución más limpia, en especial si estás compilando un componente web y tratando de mantener todo ordenado y aislado.

Con los eventos del puntero, se obtiene una solución mucho mejor: puedes capturar el puntero para asegurarte de obtener ese evento pointerup (o cualquiera de sus amigos esquivos).

const foo = document.querySelector('#foo');
foo
.addEventListener('pointerdown', ev => {
    console
.log('Button down, capturing!');
   
// Every pointer has an ID, which you can read from the event.
    foo
.setPointerCapture(ev.pointerId);
});

foo
.addEventListener('pointerup',
    ev
=> console.log('Button up. Every time!'));

Navegadores compatibles

En el momento de escribir este artículo, los eventos del puntero son compatibles con Internet Explorer 11, Microsoft Edge, Chrome y Opera, y son parcialmente compatibles con Firefox. Puedes encontrar una lista actualizada en caniuse.com.

Puedes usar el polyfill de eventos del puntero para completar los espacios. Como alternativa, comprobar la compatibilidad del navegador durante el tiempo de ejecución es sencillo:

if (window.PointerEvent) {
   
// Yay, we can use pointer events!
} else {
   
// Back to mouse and touch events, I guess.
}

Los eventos del puntero son una opción fantástica para la mejora progresiva: solo debes modificar tus métodos de inicialización para realizar la verificación anterior, agregar controladores de eventos del puntero en el bloque if y mover los controladores de eventos del mouse o del toque al bloque else.

Pruébalas y danos tu opinión.