Cómo corregir la inestabilidad del diseño

Una explicación sobre el uso de WebPageTest para identificar y solucionar problemas de inestabilidad de diseño

En una publicación anterior, escribí sobre la medición del Cambio de diseño acumulado (CLS) en WebPageTest. 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 de una página para tratar de comprender qué podría estar causando la inestabilidad y tratar de solucionar los problemas.

Cómo medir los cambios de diseño

Con la API de inestabilidad de diseño, podemos obtener una lista de todos los eventos de cambio de diseño de 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 ayudar a determinar qué podría haber causado el cambio. Volvamos a WebPageTest para un entorno de lab y realizar más pruebas.

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

Al igual que la medición de CLS en WebPageTest, la medición de cambios de diseño individuales requerirá una métrica personalizada. Afortunadamente, el proceso es más sencillo ahora que Chrome 77 es estable. La API de inestabilidad de diseño está habilitada de forma predeterminada, por lo que usted debería 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 tener que preocuparte por las marcas de la línea de comandos ni por el uso de Canary.

Entonces, modifiquemos esa secuencia de comandos 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 en esta secuencia de comandos 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 real de carga 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 de LayoutShifts tiene este valor:

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

En resumen, hay un solo cambio de diseño del 34.2% que se produce a los 3,087 ms. Para ayudar a identificar la causa, 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 película que muestran capturas de pantalla antes y después del cambio de diseño.

Desplazarse hasta la marca de ~3 segundos en la tira de película nos muestra 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. La tabla está vacía al principio, por lo que el cambio se debe a esperar a que se llene cuando se carguen los resultados.

Encabezado de fuente web que aparece de la nada
El encabezado de fuente web aparece de la nada.

Pero eso no es todo. Cuando la página está visualmente completa 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 utiliza una fuente web y no ha tomado ninguna medida para optimizar la representación. En realidad, el diseño no parece cambiar cuando esto sucede, pero tener que esperar tanto para leer el título sigue siendo una mala experiencia del usuario.

Cómo corregir la inestabilidad del diseño

Ahora que sabemos que la tabla generada de forma asíncrona está provocando que un tercio del viewport cambie, es momento de arreglarlo. No conocemos el contenido de la tabla hasta que se carguen los resultados de JSON, pero podemos completar la tabla con algún tipo de datos de marcador de posición para que el diseño en sí 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 del marcador de posición se generan de forma aleatoria antes de ordenarse. Incluye el carácter "█" que se repite 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 reducir la saturación de todos los colores de la tabla y aclarar que los datos aún no están completamente cargados.

El aspecto de los marcadores de posición que usas no importa para la estabilidad del diseño. El propósito de los marcadores de posición es garantizar a los usuarios que el contenido está disponible y que la página no está dañada.

A continuación, se muestra el aspecto de 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.

Abordar el problema de las fuentes web es mucho más sencillo. Como el sitio usa Google Fonts, solo debemos 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 fuente, lo que permitirá que el navegador renderice texto en una fuente de resguardo de inmediato. Este es 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 con WebPageTest, podemos generar una comparación del antes y el después para visualizar la diferencia y medir el nuevo grado de inestabilidad de diseño:

Tira de película WebPageTest que muestra ambos sitios que se cargan en paralelo con las optimizaciones de diseño y sin ellas
La tira de película de WebPageTest muestra ambos sitios que se cargan en paralelo con las optimizaciones de diseño y sin ellas.
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

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

También está claro a partir de la tira de película que la fuente <h1> recurre inmediatamente 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 de diseño a WebPageTest, compara los resultados con la tira de película de carga visual para identificar los culpables e implementa una corrección usando marcadores de posición para reservar el espacio de la pantalla.

(Una cosa más) Medir la inestabilidad de 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 en realidad esté mejorando. ¿No es por eso que estamos tratando de mejorar el sitio en primer lugar?

Lo que sería genial es que comencemos a medir las experiencias de inestabilidad de diseño de usuarios reales junto con nuestras métricas tradicionales de rendimiento web. Esta es una parte fundamental del ciclo de reacción de optimización, ya que tener datos del campo nos indica dónde están los problemas y si nuestras correcciones tuvieron una diferencia positiva.

Además de recopilar tus propios datos de inestabilidad de diseño, consulta el Informe de UX de Chrome, que incluye datos de Cambio de diseño acumulado proveniente de experiencias de usuarios reales en millones de sitios web. Te permite averiguar cuál es tu rendimiento (o el de tus competidores) para explorar el estado de la inestabilidad del diseño en la Web.