Eventos de entrada alineados

Dave Tapuska
Dave Tapuska

Resumen

  • Para reducir los bloqueos, Chrome 60 reduce la frecuencia de los eventos y, por lo tanto, mejora la coherencia de la latencia de fotogramas.
  • El método getCoalescedEvents(), que se introdujo en Chrome 58, proporciona la misma información sobre eventos que has tenido siempre.

Proporcionar una experiencia del usuario fluida es importante para la Web. El tiempo transcurrido entre la recepción de un evento de entrada y el momento en que se actualizan las imágenes es importante y, en general, es importante hacer menos trabajo. En las últimas versiones de Chrome, disminuimos la latencia de entrada en estos dispositivos.

Para mejorar la fluidez y el rendimiento, en Chrome 60, realizaremos un cambio que hará que estos eventos ocurran con una frecuencia menor y, al mismo tiempo, aumente el nivel de detalle de la información proporcionada. Al igual que cuando se lanzó Jelly Bean y se incorporó Choreographer, que alinea las entradas en Android, llevaremos entradas alineadas con fotogramas a la Web en todas las plataformas.

Sin embargo, a veces necesitas más eventos. Por lo tanto, en Chrome 58, implementamos un método llamado getCoalescedEvents(), que permite que tu aplicación recupere la ruta completa del puntero incluso mientras recibe menos eventos.

Hablemos primero de la frecuencia de los eventos.

Disminuye la frecuencia de los eventos

Comprendamos algunos conceptos básicos: las pantallas táctiles proporcionan una entrada a una frecuencia de 60-120 Hz, y los mouses la envían normalmente a 100 Hz (pero puede ser de hasta 2,000 Hz). Sin embargo, la frecuencia de actualización típica de un monitor es de 60 Hz. ¿Qué significa eso realmente? Significa que recibimos una entrada a una tasa más alta con la que actualizamos la pantalla. Veamos un cronograma de rendimiento de Herramientas para desarrolladores para una app de pintura de lienzos simple.

En la siguiente imagen, con la entrada alineada con requestAnimationFrame() inhabilitada, puedes ver varios bloques de procesamiento por fotograma con una latencia de fotogramas inconsistente. Los pequeños bloques amarillos indican pruebas de posicionamiento para elementos como el objetivo del evento de DOM, el despacho del evento, la ejecución de JavaScript, la actualización del nodo colocado y, posiblemente, recalcular el diseño y los estilos.

Un cronograma de rendimiento que muestra una latencia de fotogramas inconsistente

Entonces, ¿por qué estamos haciendo un trabajo adicional que no causa actualizaciones visuales? Idealmente, no queremos hacer ningún trabajo que no beneficie al usuario en última instancia. A partir de Chrome 60, la canalización de entrada retrasará el envío de eventos continuos (wheel, mousewheel, touchmove, pointermove, mousemove) y los enviará justo antes de que ocurra la requestAnimationFrame() devolución de llamada. En la siguiente imagen (con la función habilitada), verás una latencia de fotogramas más coherente y menos tiempo de procesamiento de eventos.

Estuvimos ejecutando un experimento con esta función habilitada en los canales para desarrolladores y Canary, y descubrimos que realizamos pruebas de posicionamiento un 35% menos, lo que permite que el subproceso principal esté listo para ejecutarse con más frecuencia.

Una nota importante que deben tener en cuenta los desarrolladores web es que cualquier evento discreto (como keydown, keyup, mouseup, mousedown, touchstart o touchend) que ocurra se enviará de inmediato junto con cualquier evento pendiente, lo que preserva el orden relativo. Con esta función habilitada, gran parte del trabajo se optimiza en el flujo normal de bucle de eventos, lo que proporciona un intervalo de entrada coherente. Esto integra los eventos continuos con los eventos scroll y resize, que ya se optimizaron en el flujo de bucle de eventos en Chrome.

Un cronograma de rendimiento que muestra una latencia de fotogramas relativamente coherente.

Descubrimos que la gran mayoría de las aplicaciones que consumen esos eventos no sirven para la frecuencia más alta. Android ya alineó los eventos durante varios años, por lo que no hay nada nuevo, pero es posible que los sitios experimenten eventos menos detallados en las plataformas de escritorio. Siempre hubo un problema con los subprocesos principales que se bloquearon y que causaban errores en la fluidez de las entradas. Esto significa que es posible que veas saltos de posición cada vez que la aplicación esté realizando tareas, lo que hace que sea imposible saber cómo llegó el puntero de un punto a otro.

El método getCoalescedEvents()

Como dije, hay situaciones poco frecuentes en las que la aplicación preferiría conocer la ruta completa del puntero. Por lo tanto, para corregir el caso en el que se observan saltos importantes y la frecuencia de eventos reducida, en Chrome 58 lanzamos una extensión para los eventos de puntero llamada getCoalescedEvents(). A continuación, se muestra un ejemplo de cómo se oculta el bloqueo en el subproceso principal de la aplicación si usas esta API.

Comparar eventos estándar y fusionados

En lugar de recibir un solo evento, puedes acceder al array de los eventos históricos que causaron el evento. Android, iOS y Windows tienen APIs muy similares en sus SDK nativos, y expondremos una API similar a la Web.

Por lo general, una app de dibujo puede haber dibujado un punto tras observar los desplazamientos del evento:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Este código se puede cambiar fácilmente para usar el array de eventos:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Ten en cuenta que no todas las propiedades de los eventos combinados se propagan. Dado que los eventos combinados no se despachan, sino que están listos para usarse, no se probaron. Algunos campos, como currentTarget y eventPhase, tendrán sus valores predeterminados. Llamar a métodos relacionados con el despacho, como stopPropagation() o preventDefault(), no tendrá ningún efecto en el evento superior.