Señalar el camino a seguir

Sérgio Gomes

Apuntar a objetos en la Web solía ser simple. Tenías un mouse, lo moviste, a veces presionabas botones y eso fue todo. Todo lo que no era un mouse se emulaba como uno solo, y los desarrolladores sabían exactamente con qué confiar.

Sin embargo, ser simple no necesariamente significa bueno. Con el tiempo, se volvió cada vez más importante que no todo fuera (o fingiera ser) un mouse: podías tener bolígrafos sensibles a la presión y que se inclinaban hacia una gran libertad creativa; podías usar los dedos, de modo que todo lo que necesitabas eran el dispositivo y la mano. Y ¿por qué no usar más de un dedo mientras lo estás haciendo?

Hace tiempo que usamos los eventos táctiles para ayudarnos con eso, pero son una API completamente diferente, específica para los controles táctiles, lo que te obliga a codificar dos modelos de eventos separados si deseas admitir el mouse y el modo táctil. Chrome 55 incluye un nuevo estándar que unifica ambos modelos: los eventos del puntero.

Un solo modelo de evento

Los eventos de puntero unifican el modelo de entrada del puntero para el navegador y unen el modo táctil, los bolígrafos y los mouses 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.'));

A continuación, se muestra una lista de todos los eventos disponibles, que deberían resultarte bastante conocidos si conoces los eventos del mouse:

pointerover El puntero ingresó en el cuadro delimitador del elemento. Esto sucede inmediatamente en los dispositivos que admiten el desplazamiento del mouse o antes de un evento pointerdown en el caso de los dispositivos que no lo hacen.
pointerenter Es similar a pointerover, pero no muestra burbujas y controla los subordinados de manera diferente. Detalles sobre la especificación.
pointerdown El puntero entró en el estado de botón activo y se presiona un botón o se establece un contacto, según la semántica del dispositivo de entrada.
pointermove El puntero cambió de posición.
pointerup El puntero salió del estado del botón activo.
pointercancel Ocurrió un error, por lo que es poco probable que el puntero emita más eventos. Esto significa que debes cancelar cualquier acción en curso y volver a un estado de entrada neutral.
pointerout El puntero salió del cuadro delimitador del elemento o la pantalla. También después de un pointerup, si el dispositivo no admite el desplazamiento del cursor.
pointerleave Es similar a pointerout, pero no muestra burbujas y controla los subordinados de manera diferente. Detalles sobre la especificación.
gotpointercapture El elemento recibió la captura de puntero.
lostpointercapture Se lanzó el puntero que se estaba capturando.

Diferentes tipos de entrada

En general, los eventos de puntero te permiten escribir código de manera 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 colocar el cursor sobre un elemento. Sin embargo, si deseas diferenciar diferentes tipos de dispositivos de entrada, quizás para proporcionar código o funciones independientes para diferentes entradas, puedes hacerlo desde los mismos controladores de eventos con la propiedad pointerType de la interfaz PointerEvent. Por ejemplo, si codificas un panel lateral de navegación lateral, 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 táctiles, se usan ciertos gestos para desplazar, acercar o actualizar la página. En el caso de eventos táctiles, seguirás recibiendo eventos mientras se realicen estas acciones predeterminadas. Por ejemplo, touchmove se activará 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 necesitarías usar objetos de escucha de eventos pasivos para lograr el mismo nivel de respuesta.

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

auto Predeterminado; el navegador puede realizar cualquier acción predeterminada.
none El navegador no tiene permitido 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 suelta el botón que está fuera de tu destino de clics? ¿No? Bueno, quizás sea solo yo.

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

Los eventos de puntero son una solución mucho mejor: puedes capturar el puntero para asegurarte de recibir ese evento pointerup (o cualquier otro de sus amigos poco conocidos).

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

Al momento de la redacción, los eventos de puntero son compatibles con Internet Explorer 11, Microsoft Edge, Chrome y Opera, y parcialmente con Firefox. Puedes encontrar una lista actualizada en caniuse.com.

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

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

Los eventos de puntero son un excelente candidato para la mejora progresiva. Simplemente modifica los métodos de inicialización para realizar la verificación anterior, agrega controladores de eventos del puntero en el bloque if y mueve los controladores de eventos táctiles o del mouse al bloque else.

¡No te lo pierdas! Pruébalos y danos tu opinión.