Cómo evitar el uso de pinturas innecesarias

Introducción

Pintar los elementos de un sitio o una aplicación puede resultar muy costoso y tener un efecto negativo en el rendimiento del tiempo de ejecución. En este artículo, echaremos un vistazo rápido a lo que puede activar la pintura en el navegador y cómo evitar que se realicen 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 mediante un proceso bastante complejo. Se comienza por leer el lenguaje de marcado y, a partir de este, se crea un árbol del DOM. Hace algo similar con el CSS y, a partir de eso, crea el CSSOM. Luego, se combinan el DOM y el CSSOM y, con el tiempo, se obtiene una estructura a partir de la cual podemos comenzar a pintar algunos píxeles.

El proceso de pintura en sí es interesante. En Chrome, un software llamado Skia rasteriza ese árbol combinado de DOM y CSS. Si alguna vez jugaste con el elemento canvas, la API de Skia te resultaría 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 extraen en una colección de llamadas de Skia que se pueden ejecutar, y el resultado es un conjunto de mapas de bits. Estos mapas de bits se suben a la GPU, la cual ayuda componiéndolos juntos para proporcionarnos la imagen final en la pantalla.

Dom a píxeles

Lo que hay que tener en cuenta es que la carga de trabajo de Skia se ve directamente afectada por los estilos que aplicas a tus elementos. Si usas estilos algorítmicos pesados, Skia tendrá que trabajar más. Colt McAnlis escribió un artículo sobre cómo CSS afecta el peso de renderización de la página, por lo que deberías leerlo para obtener más información.

Dicho todo esto, el trabajo de pintura lleva tiempo en completarse y, si no lo reducimos, superaremos nuestro presupuesto de fotogramas de ~16 ms. Los usuarios notarán que omitimos fotogramas y los verán como bloqueos, lo que perjudicará la experiencia del usuario en nuestra app. Como no queremos eso, veamos qué tipos de elementos hacen que sea necesario trabajo de pintura y qué podemos hacer al respecto.

Desplazamiento

Cada vez que te desplaces hacia arriba o hacia abajo en el navegador, debes volver a renderizar el contenido para que aparezca en pantalla. Si todo está bien, será solo un área pequeña, pero incluso si ese es el caso, se podrían aplicar estilos complejos a los elementos que deben dibujarse. El hecho de que tengas un área pequeña para pintar no significa que vaya a realizarla rápidamente.

Para ver qué áreas se vuelven a pintar, puedes usar la función “Mostrar rectángulos de pintura” en Herramientas para desarrolladores de Chrome (solo presiona el engranaje de la esquina inferior derecha). Luego, con las Herramientas para desarrolladores abierto, interactúa con tu página. Verás rectángulos intermitentes que muestran dónde y cuándo Chrome pintó una parte de ella.

Mostrar rectángulos de pintura en las Herramientas para desarrolladores de Chrome
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 desplazan bien y no les gusta. Por lo tanto, tenemos un interés particular en mantener el trabajo de pintura ligero durante los desplazamientos para que los usuarios no vean bloqueos.

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

Interacciones

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

Todo el mundo quiere animaciones de interacción agradables y fluidas, así que una vez más tendremos que ver si los estilos que cambian en nuestra animación nos están cuestando demasiado tiempo.

Una combinación lamentable

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 para mí interactuar" sin darte cuenta con un elemento a medida que me desplazo por él, lo que activa una pintura costosa. Eso, a su vez, podría hacerme superar mi presupuesto de fotogramas de ~16.7 ms (el tiempo que necesitamos para mantenernos por debajo de los 60 fotogramas por segundo). Creé una demostración para mostrarte exactamente lo que quiero decir. Con suerte, a medida que te desplaces y muevas el mouse, notarás que aparecen los efectos de desplazamiento. Veamos qué hace Herramientas para desarrolladores de Chrome:

Las Herramientas para desarrolladores de Chrome muestran fotogramas costosos
Las Herramientas para desarrolladores de Chrome muestran marcos costosos

En la imagen de arriba, se puede ver que Herramientas para desarrolladores está registrando el trabajo de pintura cuando me desplazo sobre uno de los bloques. En la demostración, usé algunos estilos muy pesados para dejar en claro, así que estoy aplicando el límite de fotogramas ocasionalmente. Lo último que quiero es hacer este trabajo de pintura innecesariamente, especialmente durante un desplazamiento cuando hay otros trabajos para hacer.

Entonces, ¿cómo podemos evitar que esto suceda? Mientras tanto, 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 necesitemos realizar pinturas de interacción costosas. Después de detenerte el tiempo suficiente, consideramos que es seguro volver a activarlos.

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 hacer un seguimiento de si los efectos del desplazamiento están "permitidos" 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 los usuarios que disfrutan de tu aplicación, y siempre debes tratar de mantener tu carga de trabajo de pintura por debajo de 16 ms. Para ayudarte a hacerlo, debes realizar la integración con Herramientas para desarrolladores durante todo el proceso de desarrollo, de modo que puedas identificar y solucionar los cuellos de botella que surgen.

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

Observe sus sitios y aplicaciones. ¿Se podrían hacer con un poco de protección de pintura?