Cómo funciona el navegador web moderno (parte 4)

Mariko Kosaka

La entrada llega al compositor

Esta es la última parte de la serie de blogs de 4 partes que analiza Chrome y cómo controla nuestro código para mostrar un sitio web. En la publicación anterior, analizamos el proceso de renderización y aprendimos sobre el compositor. En esta publicación, veremos cómo el compositor habilita una interacción fluida cuando llega la entrada del usuario.

Eventos de entrada desde el punto de vista del navegador

Cuando escuchas "eventos de entrada", es posible que solo pienses en escribir en un cuadro de texto o hacer clic con el mouse, pero desde el punto de vista del navegador, la entrada significa cualquier gesto del usuario. El desplazamiento de la rueda del mouse es un evento de entrada, y el toque o el desplazamiento del mouse también lo son.

Cuando se produce un gesto del usuario, como un toque en una pantalla, el proceso del navegador es el que recibe el gesto al principio. Sin embargo, el proceso del navegador solo sabe dónde se produjo ese gesto, ya que el proceso del renderizador controla el contenido dentro de una pestaña. Por lo tanto, el proceso del navegador envía el tipo de evento (como touchstart) y sus coordenadas al proceso del renderizador. El proceso del renderizador controla el evento de forma adecuada, ya que encuentra el destino del evento y ejecuta los objetos de escucha de eventos conectados.

evento de entrada
Figura 1: Evento de entrada enrutado a través del proceso del navegador al proceso del renderizador

El compositor recibe eventos de entrada

Figura 2: Vista en pantalla sobre las capas de la página

En la publicación anterior, vimos cómo el compositor podía controlar el desplazamiento de forma fluida mediante la composición de capas rasterizadas. Si no hay objetos de escucha de eventos de entrada conectados a la página, el subproceso de compositor puede crear un nuevo fotograma compuesto completamente independiente del subproceso principal. Pero, ¿qué sucede si se adjuntan algunos objetos de escucha de eventos a la página? ¿Cómo sabría el subproceso del compositor si se debe controlar el evento?

Información sobre la región desplazable no rápida

Dado que ejecutar JavaScript es la tarea del subproceso principal, cuando se compone una página, el subproceso del compositor marca una región de la página que tiene controladores de eventos adjuntos como "Región no desplazable rápidamente". Con esta información, el subproceso de compositor puede asegurarse de enviar el evento de entrada al subproceso principal si el evento ocurre en esa región. Si el evento de entrada proviene de fuera de esta región, el subproceso del compositor continúa componiendo un nuevo fotograma sin esperar al subproceso principal.

región desplazable no rápida limitada
Figura 3: Diagrama de la entrada descrita a la región desplazable no rápida

Ten cuidado cuando escribas controladores de eventos

Un patrón común de control de eventos en el desarrollo web es la delegación de eventos. Dado que los eventos se expanden, puedes adjuntar un controlador de eventos en el elemento más alto y delegar tareas según el objetivo del evento. Es posible que hayas visto o escrito código como el siguiente.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Dado que solo debes escribir un controlador de eventos para todos los elementos, la ergonomía de este patrón de delegación de eventos es atractiva. Sin embargo, si observas este código desde el punto de vista del navegador, ahora toda la página está marcada como una región desplazable no rápida. Esto significa que, incluso si a tu aplicación no le importa la entrada de ciertas partes de la página, el subproceso del compositor debe comunicarse con el subproceso principal y esperarlo cada vez que llega un evento de entrada. Por lo tanto, se inhabilita la capacidad de desplazamiento suave del compositor.

región de página completa que no se puede desplazar con rapidez
Figura 4: Diagrama de la entrada descrita a la región desplazable no rápida que cubre una página completa

Para evitar que esto suceda, puedes pasar opciones passive: true en tu objeto de escucha de eventos. Esto le indica al navegador que aún quieres escuchar el evento en el subproceso principal, pero el compositor también puede seguir adelante y componer un nuevo fotograma.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Verifica si el evento se puede cancelar

desplazamiento de página
Figura 5: Una página web con parte de la página fijada al desplazamiento horizontal

Imagina que tienes un cuadro en una página en el que quieres limitar la dirección de desplazamiento solo al desplazamiento horizontal.

El uso de la opción passive: true en tu evento del puntero significa que el desplazamiento de la página puede ser fluido, pero es posible que el desplazamiento vertical haya comenzado cuando quieras preventDefault para limitar la dirección del desplazamiento. Puedes verificar esto con el método event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Como alternativa, puedes usar una regla CSS como touch-action para eliminar por completo el controlador de eventos.

#area {
  touch-action: pan-x;
}

Cómo encontrar el objetivo del evento

prueba de posicionamiento
Figura 6: El subproceso principal observa los registros de pintura y pregunta qué se dibuja en el punto x.y.

Cuando el subproceso de compositor envía un evento de entrada al subproceso principal, lo primero que se ejecuta es una prueba de hit para encontrar el objetivo del evento. La prueba de hit usa datos de registros de pintura que se generaron en el proceso de renderización para averiguar qué hay debajo de las coordenadas del punto en el que ocurrió el evento.

Minimiza los envíos de eventos al subproceso principal

En la publicación anterior, analizamos cómo nuestra pantalla típica actualiza la pantalla 60 veces por segundo y cómo debemos seguir el ritmo de la cadencia para lograr una animación fluida. En el caso de la entrada, un dispositivo con pantalla táctil típico entrega eventos táctiles de 60 a 120 veces por segundo, y un mouse típico entrega eventos 100 veces por segundo. El evento de entrada tiene una fidelidad más alta que la que puede actualizar nuestra pantalla.

Si se envía un evento continuo como touchmove al subproceso principal 120 veces por segundo, es posible que se active una cantidad excesiva de pruebas de hit y ejecución de JavaScript en comparación con la lentitud con la que se puede actualizar la pantalla.

eventos sin filtrar
Figura 7: Eventos que inundan el cronograma de fotogramas y causan un bloqueo de la página

Para minimizar las llamadas excesivas al subproceso principal, Chrome une eventos continuos (como wheel, mousewheel, mousemove, pointermove y touchmove) y retrasa el envío hasta justo antes del siguiente requestAnimationFrame.

eventos fusionados
Figura 8: Es el mismo cronograma que antes, pero el evento se une y retrasa.

Cualquier evento discreto, como keydown, keyup, mouseup, mousedown, touchstart y touchend, se envía de inmediato.

Usa getCoalescedEvents para obtener eventos intrafotogramas

Para la mayoría de las aplicaciones web, los eventos fusionados deberían ser suficientes para proporcionar una buena experiencia del usuario. Sin embargo, si compilas elementos como una aplicación de dibujo y colocas una ruta basada en coordenadas touchmove, es posible que pierdas las coordenadas intermedias para dibujar una línea suave. En ese caso, puedes usar el método getCoalescedEvents en el evento del puntero para obtener información sobre esos eventos fusionados.

getCoalescedEvents
Figura 9: Ruta de gesto táctil suave a la izquierda, ruta limitada combinada a la derecha
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Próximos pasos

En esta serie, analizamos el funcionamiento interno de un navegador web. Si nunca te preguntaste por qué DevTools recomienda agregar {passive: true} a tu controlador de eventos o por qué deberías escribir el atributo async en tu etiqueta de secuencia de comandos, espero que esta serie te ayude a comprender por qué un navegador necesita esa información para proporcionar una experiencia web más rápida y fluida.

Cómo usar Lighthouse

Si quieres que tu código sea amigable con el navegador, pero no tienes idea de por dónde empezar, Lighthouse es una herramienta que ejecuta una auditoría de cualquier sitio web y te brinda un informe sobre lo que se está haciendo bien y lo que se debe mejorar. Leer la lista de auditorías también te da una idea de qué tipo de aspectos le interesan a un navegador.

Obtén información para medir el rendimiento

Los ajustes de rendimiento pueden variar según los diferentes sitios, por lo que es fundamental que midas el rendimiento de tu sitio y decidas qué es lo más adecuado para él. El equipo de Chrome DevTools tiene algunos instructivos sobre cómo medir el rendimiento de tu sitio.

Agrega la Política de funciones a tu sitio

Si quieres dar un paso adicional, la política de funciones es una nueva función de la plataforma web que puede servirte como protección cuando compilas tu proyecto. Si activas la política de funciones, se garantiza el comportamiento determinado de tu app y se evita que cometas errores. Por ejemplo, si quieres asegurarte de que tu app nunca bloquee el análisis, puedes ejecutarla en la política de secuencias de comandos síncronas. Cuando se habilita sync-script: 'none', se impedirá que se ejecute el JavaScript que bloquea el analizador. Esto evita que tu código bloquee el analizador, y el navegador no tiene que preocuparse por pausarlo.

Conclusión

Gracias

Cuando comencé a crear sitios web, casi solo me preocupaba cómo escribiría mi código y qué me ayudaría a ser más productivo. Esos aspectos son importantes, pero también debemos pensar en cómo el navegador toma el código que escribimos. Los navegadores modernos invierten y siguen invirtiendo en formas de proporcionar una mejor experiencia web a los usuarios. Si organizamos nuestro código para que sea amigable con el navegador, a su vez, mejoraremos la experiencia del usuario. Espero que te unas a mí en la búsqueda de ser amable con los navegadores.

Muchas gracias a todos los que revisaron los primeros borradores de esta serie, incluidos (sin limitaciones): Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Nasko Oskov y Charlie Reis.

¿Te gustó esta serie? Si tienes alguna pregunta o sugerencia para la próxima publicación, me encantaría escucharla en la sección de comentarios a continuación o en @kosamari en Twitter.