Cómo evitar el uso de pinturas innecesarias

Introducción

Pintar los elementos de un sitio o una aplicación puede ser muy costoso y puede tener un efecto negativo en el rendimiento del entorno de ejecución. En este artículo, hacemos un breve repaso de lo que puede activar la pintura en el navegador y cómo puedes evitar que se produzcan pinturas innecesarias.

Pintura: Un recorrido súper rápido

Una de las tareas principales que debe realizar un navegador es convertir tu DOM y CSS en píxeles en la pantalla, y lo hace a través de un proceso bastante complejo. Comienza por leer el marcado y, a partir de este, crea un árbol de DOM. Hace algo similar con el CSS y, a partir de eso, crea el CSSOM. Luego, se combinan el DOM y el CSSOM y, finalmente, llegamos a una estructura desde la que podemos comenzar a pintar algunos píxeles.

El proceso de pintura en sí es interesante. En Chrome, ese árbol combinado de DOM y CSS se rasteriza con un software llamado Skia. Si alguna vez jugaste con el elemento canvas, la API de Skia te resultará muy familiar. Hay muchas funciones de estilo moveTo y lineTo, así como muchas más avanzadas. Básicamente, todos los elementos que se deben pintar se destilan en una colección de llamadas a Skia que se pueden ejecutar, y el resultado es un conjunto de mapas de bits. Estos mapas de bits se suben a la GPU, que los combina para mostrar la imagen final en la pantalla.

De DOM a píxeles

En conclusión, la carga de trabajo de Skia se ve directamente afectada por los estilos que aplicas a los elementos. Si usas estilos algorítmicamente pesados, Skia tendrá más trabajo que hacer. Colt McAnlis escribió un artículo sobre cómo el CSS afecta el peso de renderización de la página, así que deberías leerlo para obtener más información.

Dicho esto, el trabajo de pintura lleva tiempo y, si no lo reducimos, superaremos nuestro presupuesto de fotogramas de alrededor de 16 ms. Los usuarios notarán que omitimos fotogramas y lo verán como un error, lo que, en última instancia, perjudica la experiencia del usuario de nuestra app. No queremos que eso suceda, así que veamos qué tipos de problemas hacen que sea necesario el trabajo de pintura y qué podemos hacer al respecto.

Desplazamiento

Cada vez que te desplazas hacia arriba o hacia abajo en el navegador, se debe volver a pintar el contenido antes de que aparezca en pantalla. Si todo va bien, solo será un área pequeña, pero, incluso si ese es el caso, los elementos que se deben dibujar podrían tener estilos complejos aplicados. Por lo tanto, el hecho de que tengas un área pequeña para pintar no significa que el proceso será rápido.

Para ver qué áreas se vuelven a pintar, puedes usar la función "Show Paint Rectangles" en DevTools de Chrome (solo presiona el pequeño engranaje en la esquina inferior derecha). Luego, con DevTools abierto, simplemente interactúa con tu página y verás rectángulos intermitentes que muestran dónde y cuándo Chrome pintó una parte de tu página.

Cómo mostrar rectángulos de pintura en las Herramientas para desarrolladores de Chrome
Cómo mostrar rectángulos de pintura en las Herramientas para desarrolladores de Chrome

El rendimiento del desplazamiento es fundamental para el éxito de tu sitio. Los usuarios realmente notan cuando tu sitio o aplicación no se desplaza bien y no les gusta. Por lo tanto, tenemos un interés personal en mantener el trabajo de pintura ligero durante los desplazamientos para que los usuarios no vean interrupciones.

Anteriormente, escribí un artículo sobre el rendimiento del desplazamiento, así que consulta ese artículo si quieres obtener más información sobre los aspectos específicos del rendimiento del desplazamiento.

Interacciones

Las interacciones son otra causa del trabajo de pintura: desplazamiento, clics, toques o arrastres. Cada vez que el usuario realice una de esas interacciones, por ejemplo, colocar el cursor sobre un elemento, Chrome deberá volver a pintar el elemento afectado. Además, al igual que con el desplazamiento, si se requiere una pintura grande y compleja, verás una disminución en la velocidad de fotogramas.

Todos quieren animaciones de interacción agradables y fluidas, por lo que, nuevamente, tendremos que ver si los estilos que cambian en nuestra animación nos están costando demasiado tiempo.

Una combinación desafortunada

Una demostración con pinturas costosas
Una demostración con pinturas costosas

¿Qué sucede si me desplazo y muevo el mouse al mismo tiempo? Es perfectamente posible que involuntariamente "interactúe" con un elemento mientras me desplacé por él, lo que activaría una pintura costosa. Eso, a su vez, podría forzarme a través de mi presupuesto de fotogramas de ~16.7 ms (el tiempo que necesitamos para no superar ese límite para alcanzar los 60 fotogramas por segundo). Creé una demostración para mostrarte exactamente a qué me refiero. Con suerte, a medida que te desplazas y mueves el mouse, verás que se activan los efectos de desplazamiento, pero veamos qué hace DevTools de Chrome con ellos:

Chrome DevTools muestra marcos costosos
Las Herramientas para desarrolladores de Chrome muestran marcos costosos

En la imagen de arriba, puedes ver que Herramientas para desarrolladores registra el trabajo de pintura cuando me desplazo sobre uno de los bloques. Me puse a usar algunos estilos muy pesados en mi demostración para entender el punto, así que estoy ampliando mi presupuesto de fotogramas ocasionalmente. Lo último que quiero es tener que hacer este trabajo de pintura innecesariamente, y especialmente durante un desplazamiento cuando hay otro trabajo por hacer.

Entonces, ¿cómo podemos evitar que esto suceda? La solución es bastante fácil de implementar. El truco aquí es adjuntar un controlador scroll que inhabilite los efectos de desplazamiento y establezca un temporizador para volver a habilitarlos. Esto significa que garantizamos que, cuando te desplaces, no necesitaremos realizar ninguna pintura de interacción costosa. Cuando te detengas durante el tiempo suficiente, consideraremos que es seguro volver a encenderlos.

Este es el código:

// Used to track the enabling of hover effects
var enableTimer = 0;

/*
 * Listen for a scroll and use that to remove
 * the possibility of hover effects
 */
window.addEventListener('scroll', function() {
  clearTimeout(enableTimer);
  removeHoverClass();

  // enable after 1 second, choose your own value here!
  enableTimer = setTimeout(addHoverClass, 1000);
}, false);

/**
 * Removes the hover class from the body. Hover styles
 * are reliant on this class being present
 */
function removeHoverClass() {
  document.body.classList.remove('hover');
}

/**
 * Adds the hover class to the body. Hover styles
 * are reliant on this class being present
 */
function addHoverClass() {
  document.body.classList.add('hover');
}

Como puedes ver, usamos una clase en el cuerpo para realizar un seguimiento de si se "permiten o no los efectos de desplazamiento", y los estilos subyacentes dependen de que esta clase esté presente:

/* Expect the hover class to be on the body
 before doing any hover effects */
.hover .block:hover {
 
}

Eso es todo.

Conclusión

El rendimiento de la renderización es fundamental para que los usuarios disfruten de tu aplicación, y siempre debes intentar mantener la carga de trabajo de pintura por debajo de 16 ms. Para ayudarte a hacerlo, debes integrar DevTools durante todo el proceso de desarrollo para identificar y corregir los cuellos de botella a medida que surjan.

Las interacciones involuntarias, en especial en los elementos con mucho contenido de pintura, pueden ser muy costosas y anular el rendimiento de la renderización. Como viste, podemos usar un pequeño fragmento de código para solucionarlo.

Observa tus sitios y aplicaciones. ¿Podrían necesitar un poco de protección?