Cómo corregir la inestabilidad del diseño

Explicación del uso de WebPageTest para identificar y corregir problemas de inestabilidad del diseño.

En una publicación anterior, escribí sobre cómo medir el cambio de diseño acumulado (CLS) en WebPageTest. El CLS es una agregación de todos los cambios de diseño, por lo que, en esta publicación, pensé que sería interesante profundizar y analizar cada cambio de diseño individual en una página para tratar de comprender qué podría estar causando la inestabilidad y, de hecho, intentar solucionar los problemas.

Cómo medir los cambios de diseño

Con la API de Layout Instability, podemos obtener una lista de todos los eventos de cambio de diseño en una página:

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

Esto produce un array de cambios de diseño que no están precedidos por eventos de entrada:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

En este ejemplo, hubo un solo cambio muy pequeño del 0.01% a los 210 ms.

Conocer la hora y la gravedad del cambio es útil para determinar qué pudo haberlo causado. Volvamos a WebPageTest para usar un entorno de laboratorio y realizar más pruebas.

Cómo medir los cambios de diseño en WebPageTest

Al igual que con la medición del CLS en WebPageTest, la medición de los cambios de diseño individuales requerirá una métrica personalizada. Por suerte, el proceso es más fácil ahora que Chrome 77 es estable. La API de Layout Instability está habilitada de forma predeterminada, por lo que deberías poder ejecutar ese fragmento de JS en cualquier sitio web dentro de Chrome 77 y obtener resultados de inmediato. En WebPageTest, puedes usar el navegador Chrome predeterminado y no preocuparte por las marcas de línea de comandos ni por usar Canary.

Por lo tanto, modifiquemos ese código para generar una métrica personalizada para WebPageTest:

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

La promesa de este script se resuelve en una representación JSON del array en lugar del array en sí. Esto se debe a que las métricas personalizadas solo pueden producir tipos de datos primitivos, como cadenas o números.

El sitio web que usaré para la prueba es ismyhostfastyet.com, un sitio que creé para comparar el rendimiento de carga real de los hosts web.

Cómo identificar las causas de la inestabilidad del diseño

En los resultados, podemos ver que la métrica personalizada LayoutShifts tiene este valor:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

En resumen, se produce un solo cambio de diseño del 34.2% a los 3087 ms. Para ayudar a identificar el problema, usemos la vista de tira de película de WebPageTest.

Dos celdas en la tira de película que muestran capturas de pantalla antes y después del cambio de diseño.
Dos celdas en la tira de diapositivas, que muestran capturas de pantalla antes y después del cambio de diseño.

Si te desplazas hasta la marca de ~3 segundos en la tira de película, verás exactamente cuál es la causa del cambio de diseño del 34%: la tabla colorida. El sitio web recupera de forma asíncrona un archivo JSON y, luego, lo renderiza en una tabla. Inicialmente, la tabla está vacía, por lo que esperar a que se complete cuando se cargan los resultados provoca el cambio.

Aparece un encabezado de fuente web de la nada.
El encabezado de la fuente web aparece de la nada.

Pero eso no es todo. Cuando la página se completa visualmente en aproximadamente 4.3 segundos, podemos ver que el <h1> de la página "¿Mi host ya es rápido?" aparece de la nada. Esto sucede porque el sitio usa una fuente web y no tomó ninguna medida para optimizar la renderización. En realidad, el diseño no parece cambiar cuando esto sucede, pero sigue siendo una mala experiencia del usuario tener que esperar tanto para leer el título.

Cómo corregir la inestabilidad del diseño

Ahora que sabemos que la tabla generada de forma asíncrona hace que se desplace un tercio de la ventana gráfica, es hora de corregirlo. No conocemos el contenido de la tabla hasta que se cargan los resultados JSON, pero podemos completar la tabla con algún tipo de datos de marcador de posición para que el diseño sea relativamente estable cuando se renderice el DOM.

Este es el código para generar datos de marcador de posición:

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

Los datos de marcador de posición se generan de forma aleatoria antes de ordenarse. Incluye el carácter "█" repetido una cantidad aleatoria de veces para crear marcadores de posición visuales para el texto y una distribución generada de forma aleatoria de los tres valores principales. También agregué algunos estilos para quitar la saturación de todos los colores de la tabla y dejar en claro que los datos aún no se cargaron por completo.

La apariencia de los marcadores de posición que usas no afecta la estabilidad del diseño. El propósito de los marcadores de posición es asegurar a los usuarios que el contenido está en camino y que la página no está dañada.

Así se ven los marcadores de posición mientras se cargan los datos JSON:

La tabla de datos se renderiza con datos de marcador de posición.
La tabla de datos se renderiza con datos de marcador de posición.

Solucionar el problema de la fuente web es mucho más sencillo. Como el sitio usa Google Fonts, solo tenemos que pasar la propiedad display=swap en la solicitud de CSS. Eso es todo. La API de Fonts agregará el estilo font-display: swap en la declaración de la fuente, lo que permitirá que el navegador renderice texto en una fuente de resguardo de inmediato. Aquí se muestra el lenguaje de marcado correspondiente con la corrección incluida:

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

Cómo verificar las optimizaciones

Después de volver a ejecutar la página en WebPageTest, podemos generar una comparación del antes y el después para visualizar la diferencia y medir el nuevo grado de inestabilidad del diseño:

Tira de película de WebPageTest que muestra ambos sitios cargándose uno al lado del otro con y sin optimizaciones de diseño.
Tira de película de WebPageTest que muestra ambos sitios cargándose uno al lado del otro con y sin optimizaciones de diseño.
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

Según la métrica personalizada, aún se produce un cambio de diseño a los 3,071 ms (casi al mismo tiempo que antes), pero la gravedad del cambio es mucho menor: 0.005%. Puedo vivir con esto.

También se observa claramente en la tira de película que la fuente <h1> recurre de inmediato a una fuente del sistema, lo que permite que los usuarios la lean antes.

Conclusión

Es probable que los sitios web complejos experimenten muchos más cambios de diseño que en este ejemplo, pero el proceso de corrección sigue siendo el mismo: agrega métricas de inestabilidad del diseño a WebPageTest, compara los resultados con la tira de película de carga visual para identificar los problemas y, luego, implementa una corrección con marcadores de posición para reservar el espacio en pantalla.

(Una cosa más) Cómo medir la inestabilidad del diseño que experimentan los usuarios reales

Es bueno poder ejecutar WebPageTest en una página antes y después de una optimización, y ver una mejora en una métrica, pero lo que realmente importa es que la experiencia del usuario mejore. ¿No es por eso que estamos tratando de mejorar el sitio en primer lugar?

Por lo tanto, sería ideal si comenzáramos a medir las experiencias de inestabilidad del diseño de los usuarios reales junto con nuestras métricas tradicionales de rendimiento web. Esta es una parte fundamental del ciclo de retroalimentación de la optimización, ya que tener datos del campo nos indica dónde están los problemas y si nuestras correcciones marcaron una diferencia positiva.

Además de recopilar tus propios datos de inestabilidad del diseño, consulta el Chrome UX Report, que incluye datos del cambio de diseño acumulado de experiencias de usuarios reales en millones de sitios web. Te permite averiguar cómo te va a ti (o a tus competidores) o puedes usarla para explorar el estado de la inestabilidad del diseño en la Web.