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 la medición del 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 e inspeccionar cada cambio de diseño individual en una página para tratar de comprender qué podría estar causando la inestabilidad y, en realidad, intentar solucionar los problemas.

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 el momento y la gravedad del cambio es útil para acotar lo que podría haberlo causado. Regresemos a WebPageTest para obtener un entorno de lab y realizar más pruebas.

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

Al igual que con la medición de CLS en WebPageTest, la medición de 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 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 línea de comandos ni por usar Canary.

Modifiquemos esa secuencia de comandos para producir 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 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 de carga real de los hosts web.

Identifica 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, hay un solo cambio de diseño del 34.2% que se produce a los 3,087 ms. Para ayudar a identificar el culpable, 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 nos desplazamos hasta la marca de aproximadamente 3 segundos en la tira de película, se 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 esperar a que se llene cuando se carguen los resultados causa el cambio.

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

Pero eso no es todo. Cuando la página está completa visualmente a los ~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 se ha tomado ninguna medida para optimizar la renderización. En realidad, el diseño no parece cambiar cuando esto sucede, pero sigue siendo una experiencia del usuario deficiente tener que esperar tanto tiempo 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 del viewport, es hora de solucionarlo. No conocemos el contenido de la tabla hasta que se cargan los resultados JSON, pero aún podemos propagar 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 marcadores 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 los marcadores 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 aleatoriamente de los tres valores principales. También agregué algunos estilos para desaturar todo el color 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 uses no es importante para la estabilidad del diseño. El propósito de los marcadores de posición es garantizarles a los usuarios que el contenido está disponible y que la página no está dañada.

Este es 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 marcadores 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 la fuente, lo que permitirá que el navegador renderice el 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 a través de WebPageTest, podemos generar una comparación antes y después para visualizar la diferencia y medir el nuevo grado de inestabilidad del diseño:

Tira de película de WebPageTest que muestra cómo se cargan ambos sitios en paralelo con y sin optimizaciones de diseño.
Película de WebPageTest que muestra cómo se cargan ambos sitios en paralelo 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, todavía se produce un cambio de diseño a los 3071 ms (aproximadamente el mismo tiempo que antes), pero la gravedad del cambio es mucho menor: 0.005%. Puedo vivir con esto.

También se ve claramente en 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 solución sigue siendo el mismo: agrega métricas de inestabilidad del diseño a WebPageTest, realiza una verificación cruzada de los resultados con la película de carga visual para identificar los culpables y, luego, implementa una solución con marcadores de posición para reservar el espacio en pantalla.

(Una cosa más) Medición de 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 intentamos mejorar el sitio en primer lugar?

Por lo tanto, sería muy útil que comencemos a medir las experiencias de inestabilidad del diseño de los usuarios reales junto con nuestras métricas de rendimiento web tradicionales. 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 generaron una diferencia positiva.

Además de recopilar tus propios datos de inestabilidad del diseño, consulta el Informe de UX de Chrome, que incluye datos de cambio de diseño acumulativo de experiencias de usuarios reales en millones de sitios web. Te permite saber cómo te desempeñas tú (o tus competidores) o puedes usarlo para explorar el estado de inestabilidad del diseño en la Web.