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

Mariko Kosaka

Entrada al compositor

Esta es la última de la serie de blogs de 4 partes que analiza el interior de Chrome y investiga cómo maneja nuestro código para mostrar un sitio web. En la publicación anterior, vimos 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 entra el 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 en un 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 es un evento de entrada.

Cuando se produce un gesto del usuario, como tocar en una pantalla, el proceso del navegador es el que recibe el gesto en primer lugar. Sin embargo, el proceso del navegador solo reconoce dónde ocurrió el 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 procesador controla el evento de manera adecuada. Para ello, encuentra el destino del evento y ejecuta objetos de escucha de eventos adjuntos.

evento de entrada
Figura 1: Evento de entrada enrutado al proceso del navegador al proceso del procesador

El compositor recibe eventos de entrada

Figura 2: Ventana de visualización que se desplaza sobre las capas de la página

En la publicación anterior, observamos cómo el compositor puede controlar el desplazamiento sin problemas mediante la composición de capas rasterizadas. Si no se adjuntan objetos de escucha de eventos de entrada a la página, el subproceso compositor puede crear un nuevo marco compuesto por completo independiente del subproceso principal. Pero, ¿qué sucedería si se adjuntaran algunos objetos de escucha de eventos a la página? ¿Cómo averigua el subproceso compositor si se debe controlar el evento?

Información sobre las regiones en las que no es posible desplazarse rápidamente

Dado que ejecutar JavaScript es el trabajo 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 rápida de desplazamiento". Con esta información, el subproceso 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 compositor continúa con la composición del nuevo marco sin esperar al subproceso principal.

región limitada y no rápida para el desplazamiento
Figura 3: Diagrama de la entrada descrita para 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. Desde el cuadro de eventos, puedes adjuntar un controlador de eventos en el elemento superior y delegar tareas según el destino del evento. Es posible que hayas visto o escrito un código como el que se muestra a continuación.

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

Dado que solo necesitas 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 no rápida de desplazamiento. Esto significa que, incluso si a la aplicación no le interesan las entradas de ciertas partes de la página, el subproceso compositor debe comunicarse con el subproceso principal y esperarla cada vez que ingresa un evento de entrada. Por lo tanto, se pierde la capacidad de desplazamiento suave del compositor.

región no rápida de desplazamiento de página completa
Figura 4: Diagrama de la entrada descrita para la región no rápida desplazable que abarca una página completa

Para evitar que esto suceda, puedes pasar las opciones de passive: true en el objeto de escucha de eventos. Esto indica al navegador que aún deseas escuchar el evento en el subproceso principal, pero el compositor también puede crear un nuevo fotograma.

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

Verifica si se puede cancelar el evento

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

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

Si usas la opción passive: true en tu evento de puntero, 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 de 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 de CSS como touch-action para eliminar por completo el controlador de eventos.

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

Encuentra el objetivo del evento

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

Cuando el subproceso compositor envía un evento de entrada al subproceso principal, lo primero que se debe ejecutar es una prueba de posicionamiento para encontrar el destino del evento. La prueba de posicionamiento usa datos de registros de pintura que se generaron en el proceso de renderización para averiguar qué hay debajo de las coordenadas de puntos en las que se produjo el evento.

Cómo minimizar 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 mantener la cadencia para lograr una animación fluida. En cuanto a la entrada, un dispositivo de pantalla táctil típico envía un evento táctil de 60 a 120 veces por segundo, y un mouse típico envía eventos de 100 veces por segundo. El evento de entrada tiene mayor fidelidad que la que puede actualizarse la pantalla.

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

eventos sin filtrar
Figura 7: Eventos que desbordan el cronograma del fotograma y provocan el bloqueo de la página

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

eventos consolidados
Figura 8: El mismo cronograma que antes, pero el evento se combina y se retrasa

Todos los eventos discretos, como keydown, keyup, mouseup, mousedown, touchstart y touchend, se envían de inmediato.

Usa getCoalescedEvents para obtener eventos dentro del fotograma

En la mayoría de las aplicaciones web, los eventos combinados deberían ser suficientes para proporcionar una buena experiencia del usuario. Sin embargo, si compilas elementos como dibujar una aplicación y colocar un trazado basado en coordenadas touchmove, es posible que pierdas datos intermedios entre las coordenadas 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 unidos.

getCoalescedEvents
Figura 9: Ruta de gestos táctiles suaves a la izquierda y ruta limitada unida 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 pensaste en por qué DevTools recomienda agregar {passive: true} en tu controlador de eventos o por qué podrías escribir el atributo async en tu etiqueta de secuencia de comandos, esperamos 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.

Usar Lighthouse

Si deseas que tu código se adapte al navegador, pero no sabes por dónde comenzar, Lighthouse es una herramienta que ejecuta auditorías de cualquier sitio web y te brinda un informe sobre lo que se hace bien y lo que se debe mejorar. Leer la lista de auditorías también te da una idea de los aspectos que le interesan a un navegador.

Aprende a medir el rendimiento

Estos ajustes pueden variar según el sitio, por lo que es fundamental que lo midas y decidas qué opción se adapta mejor a él. El equipo de las Herramientas para desarrolladores de Chrome 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 función nueva de plataforma web que puede servirte como barandilla cuando compilas tu proyecto. Activar la política de funciones garantiza el cierto comportamiento de tu app y evita que cometas errores. Por ejemplo, si deseas asegurarte de que tu app nunca bloquee el análisis, puedes ejecutarla en una política de secuencias de comandos síncronas. Cuando se habilite sync-script: 'none', no se ejecutará JavaScript que bloquea los analizadores. De esta manera, se evita que cualquier código bloquee el analizador, y el navegador no tiene que preocuparse por detener el analizador.

Conclusión

Gracias

Cuando comencé a crear sitios web, casi solo me importaba cómo escribiría mi código y qué me ayudaría a ser más productiva. Esto es importante, pero también debemos pensar en cómo el navegador toma el código que escribimos. Los navegadores modernos han invertido, y siguen invirtiendo en maneras de proporcionar una mejor experiencia web para los usuarios. Ser agradable con el navegador y organizar nuestro código, a su vez, mejora la experiencia del usuario. ¡Espero que nos acompañes en la misión de ser amable con los navegadores!

Queremos agradecer a todos los que revisaron los primeros borradores de esta serie, incluidos, sin limitaciones, Alex Russell, Paul Ireland, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yaasklieo}.

¿Te gustó esta serie? Si tienes preguntas o sugerencias para la próxima publicación, comunícate con nosotros en la sección de comentarios que aparece a continuación o en @kosamari en Twitter.