Evitar los desplazamientos de los componentes en el diseño y mejorar la estabilidad visual de una página.
El Cumulative Layout Shift (CLS) es una métrica de los Web Vitals que mide la estabilidad visual de una página web. Se refiere a los cambios inesperados en el diseño mientras se carga el contenido, lo que puede afectar la experiencia del usuario.
Si alguna vez has intentado hacer clic en un botón antes de que finalice la carga de la página y, en el último momento, este se ha movido porque otro elemento se cargó en su lugar, has experimentado CLS. Esto ocurre cuando los elementos de la página se reacomodan sin previo aviso debido a imágenes sin dimensiones definidas, fuentes que se cargan tarde o contenido insertado de forma dinámica sin espacio reservado.
El método más efectivo para evitar cambios inesperados en el diseño es asegurarse de que cada elemento tenga un tamaño predefinido. Aquí te dejamos algunas estrategias:
Las imágenes sin dimensiones explícitas hacen que el navegador no pueda calcular su espacio antes de que se descarguen. La solución más directa es definir el width
y height
en HTML <img src="/imagen.webp" width="32px" height="32px" />
o bien en CSS usando aspect-ratio
:
img { width: 100%; aspect-ratio: 16 / 9; /* Mantiene la proporción sin distorsionar */ object-fit: cover;}
Si el contenido multimedia se carga de forma dinámica y no sabemos el tamaño de antemano también podemos usar un min-height
en el contenedor:
.video-placeholder { min-height: 300px; /* Reserva el espacio */ background-color: #f0f0f0;}
Las listas que se rellenan con datos de forma asíncrona con JavaScript pueden generar saltos en la visualización si los elementos aparecen sin reservar espacio. Esto ocurre porque, al no haber contenido inicialmente, el espacio es mínimo y cuando los datos llegan, desplazan todo el contenido siguiente hacia abajo.
Para prevenir este efecto, hay un truco que es:
height: 100vh
en el loading para que el espacio de la lista a continuación quede fuera del viewport inicial y, con media queries, ajustar la altura en pantallas más grandes. Lo importante es que el min-height sea el mismo para el loading que para la lista dinámica.Ejemplo:
<div class="dynamic-list-container"> <div id="loading" class="loading hidden"> <svg viewBox="0 0 50 50"> <circle cx="25" cy="25" r="20" stroke="#007bff" stroke-width="4" fill="none" stroke-linecap="round"></circle> </svg> </div>
<ul id="dynamicList" class="dynamic-list"> <li>Elemento 1</li> <li>Elemento 2</li> <li>Elemento 3</li> <li>Elemento 4</li> <li>Elemento 5</li> </ul></div>
Los estilos principales CSS.
.loading { min-height: 100vh; /* En móviles, empuja la lista fuera del viewport mientras está cargando */ display: flex; justify-content: center; align-items: center;}
@media (min-width: 768px) { .loading { min-height: 233px; /* En pantallas más grandes, usamos un tamaño fijo */ }}
.dynamic-list { width: 100%; display: flex; flex-direction: column; gap: 10px; padding: 0; list-style: none; min-height: 233px;}
Esta técnica evita que los cambios sean perceptibles en móviles y, en desktop, mantiene un tamaño fijo para evitar desplazamientos innecesarios.
El ejemplo es un poco complejo, pero si visualizas la siguiente demo y pruebas con distintos tamaños de pantalla usando la herramienta de Google Developer Tools y refrescas la página varias veces verás que nunca hay un salto en el contenido cuando este se muestra.
<!DOCTYPE html><html lang="es">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Evitar CLS en Listas Dinámicas</title> <style> body { font-family: Arial, sans-serif; margin: 20px; }
.dynamic-list-container { display: block; }
.loading { min-height: 100vh; /* En móviles, empuja la lista fuera del viewport mientras está cargando */ display: flex; justify-content: center; align-items: center; }
@media (min-width: 768px) { .loading { min-height: 233px; /* En pantallas más grandes, usamos un tamaño fijo */ } }
.loading svg { width: 50px; height: 50px; animation: spin 1s linear infinite; }
@keyframes spin { 100% { transform: rotate(360deg); } }
.dynamic-list { width: 100%; display: flex; flex-direction: column; gap: 10px; padding: 0; list-style: none; min-height: 233px; }
.dynamic-list li { background-color: #f0f0f0; padding: 10px; border-radius: 5px; text-align: center; }
.hidden { display: none; } </style></head>
<body>
<div class="dynamic-list-container"> <!-- SVG de carga mientras se cargan los datos --> <div id="loading" class="loading"> <svg viewBox="0 0 50 50"> <circle cx="25" cy="25" r="20" stroke="#007bff" stroke-width="4" fill="none" stroke-linecap="round" /> </svg> </div>
<ul id="dynamicList" class="dynamic-list hidden"></ul> </div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus placerat, erat in dignissim luctus, risus odio porta nulla, sed congue lorem felis eget felis.</p>
<script> // Simular carga de datos con un timeout setTimeout(() => { const list = document.getElementById('dynamicList'); const loading = document.getElementById('loading');
// Insertar elementos en la lista for (let i = 1; i <= 5; i++) { const li = document.createElement('li'); li.textContent = `Elemento ${i}`; list.appendChild(li); }
// Ocultar loading y mostrar la lista loading.classList.add('hidden'); list.classList.remove('hidden'); }, 500); </script>
</body>
</html>
Para ver bien el efecto deberías guardar este código html en local y ejecutarlo en el navegador.
El Cumulative Layout Shift es un problema común que afecta la experiencia del usuario y el rendimiento SEO. Aplicando buenas prácticas como reservar espacio con min-height
, aspect-ratio
y placeholders, es posible minimizar estos cambios y lograr una carga más estable y fluida.
Además hemos visto un ejemplo complejo pero efectivo para evitar el layout shift empujando el contenido fuera del viewport mientras este se está cargando.