¡Qué onda, futuru/a dev! Seguro te ha pasado más de una vez: entras a una página web, empiezas a leer un artículo super interesante y, de la nada, ¡pum! Un banner de notificación o anuncio aparece en la parte superior y empuja todo el contenido hacia abajo, haciéndote perder el hilo de la lectura o provocando que le des clic a algo por accidente.

Ese empujón molesto en el diseño web se conoce como Layout Shift (desplazamiento de diseño). Además de frustrar a los usuarios, a los motores de búsqueda como Google no les gusta para nada este comportamiento y sancionan a los sitios que lo sufren a través de una métrica llamada CLS (Cumulative Layout Shift).

En este tutorial aprenderás de forma muy sencilla cómo maquetar contenedores o slots de anuncios dinámicos usando HTML, CSS y un toque de JavaScript, logrando que tu página se mantenga estable sin importar cuándo termine de cargar el contenido dinámico.


¿Por qué ocurre el desplazamiento de diseño?

Cuando construyes una página web moderna, es muy habitual pedir datos a un servidor o API después de que el sitio inicial ya se renderizó en el navegador. Por ejemplo, un banner de aviso de promociones o un anuncio personalizado.

El flujo de error típico ocurre así:

  1. El HTML y CSS iniciales se descargan y se muestran en pantalla.
  2. El navegador pinta la estructura inicial (tu encabezado, tu texto, tus imágenes).
  3. Un script de JavaScript hace una petición a un servidor para traer el contenido del banner.
  4. Medio segundo después, llega la respuesta del servidor. JavaScript inserta la etiqueta <div> del banner en la parte superior del documento.
  5. El navegador recalcula el tamaño del contenido y empuja todo hacia abajo 60 o 100 píxeles.

Para solucionar esto, no necesitas frameworks avanzados ni herramientas complejas. Todo se reduce a un concepto fundamental de maquetación: reservar el espacio con anticipación.


Paso a paso: Creando un contenedor dinámico estable

Vamos a construir un ejemplo práctico desde cero. Imagina que queremos un banner superior que se cargará con datos simulados después de 1 segundo.

Paso 1: Estructura HTML básica

Comenzamos definiendo la estructura en HTML. Crearemos una etiqueta <aside> o <div> que funcionará como nuestro contenedor (slot) de la alerta dinámicas.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ejemplo de Banner Dinámico Estable</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <!-- Slot reservado para el banner dinámico -->
  <div id="announcement-slot" class="banner-slot loading">
    <!-- El contenido se cargará vía JavaScript -->
  </div>

  <header class="main-header">
    <h1>Mi Blog de Programación 8devmx</h1>
  </header>

  <main class="content">
    <h2>Aprende Desarrollo Web sin Complicaciones</h2>
    <p>Este es un texto de prueba para demostrar cómo el contenido no se mueve cuando el banner superior termina de cargar.</p>
  </main>

  <script src="app.js"></script>
</body>
</html>

Paso 2: Reservar el espacio con CSS

El truco principal está en el archivo CSS. Le diremos al contenedor de la alerta exactamente cuánta altura debe tener antes de que llegue la información desde la red.

/* Estilos generales */
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f4f6f8;
  color: #333;
}

/* 1. Reservamos la altura fija o mínima del banner */
.banner-slot {
  width: 100%;
  min-height: 60px; /* Reservamos el espacio vertical */
  background-color: #e0e0e0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.3s ease;
}

/* 2. Estado de carga visual (Skeleton Screen sencillo) */
.banner-slot.loading {
  background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

/* Animación para indicar al usuario que algo se está cargando */
@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

/* 3. Estado cuando el banner ya está cargado */
.banner-slot.loaded {
  background-color: #2b6cb0;
  color: #ffffff;
  font-weight: bold;
}

.main-header, .content {
  padding: 20px;
}

Paso 3: Cargar el contenido con JavaScript

Simularemos una llamada a un servidor remoto usando setTimeout. Cuando los datos lleguen, inyectamos el HTML pero el tamaño total de la página no va a brincar porque el espacio ya estaba ocupado por la animación de carga.

// Archivo app.js

document.addEventListener('DOMContentLoaded', () => {
  const bannerSlot = document.getElementById('announcement-slot');

  // Simulamos una petición de red con retraso de 1.5 segundos
  setTimeout(() => {
    // Datos simulados de la API
    const announcementData = {
      text: '🚀 ¡Aprovecha un 50% de descuento en nuestro curso intensivo de JS!',
      link: '#'
    };

    // Inyectamos el contenido
    bannerSlot.innerHTML = `<p>${announcementData.text} <a href="${announcementData.link}" style="color: #ffd700;">Ver más</a></p>`;
    
    // Cambiamos las clases CSS para remover el efecto de carga
    bannerSlot.classList.remove('loading');
    bannerSlot.classList.add('loaded');
  }, 1500);
});

Errores comunes al manejar contenido dinámico

  1. Usar display: none inicialmente: Si mantienes tu contenedor con display: none y luego lo cambias a display: block cuando cargan los datos, provocará exactamente el mismo brinco brusco de diseño. Usa visibilidad con reservación de espacio como min-height o placeholders (skeletons).
  2. No contemplar dispositivos móviles: Un banner que mide 60px de alto en una computadora de escritorio puede necesitar 100px en un teléfono celular porque el texto se ajusta en varias líneas. Usa Media Queries en CSS para ajustar el min-height en pantallas pequeñas.
  3. Cargar fuentes web tarde: A veces el texto dentro del banner cambia de tamaño cuando la tipografía personalizada termina de descargar. Procura usar font-display: swap en tus reglas @font-face.

Buenas prácticas recomendadas

  • Utiliza Skeleton Screens: Un indicador visual gris con animación sutil le comunica al usuario que ahí se está procesando algo, mejorando la percepción de velocidad de tu sitio.
  • Propiedad aspect-ratio: Si vas a cargar imágenes dinámicas como anuncios en forma de banner o tarjeta, usa la propiedad de CSS aspect-ratio: 16 / 9; o la medida correspondiente para reservar las proporciones de la imagen antes de que descargue.
  • Variables CSS para alturas estandarizadas: Define alturas globales en CSS (ejemplo --banner-height: 60px;) para reutilizarlas fácilmente en tu código.

Reto práctico para ti

Pon a prueba lo aprendido construyendo una tarjeta de usuario (User Card) en HTML y CSS:

  1. Crea un contenedor de 300px por 150px con un fondo de carga (skeleton screen).
  2. Mediante JavaScript y setTimeout, haz que a los 2 segundos aparezca la foto de perfil y el nombre de un usuario ficticio dentro del contenedor.
  3. Asegúrate de que el resto de los elementos de tu página no se desplacen ni un solo píxel cuando la información aparezca.

¡Inténtalo en tu editor de código favorito y compártenos tu resultado!


Fuente original

Este artículo toma como inspiración técnica la explicación sobre la arquitectura de slots dinámicos sin descalces de hidratación publicada por Pavan S Poojary en Dev.to. Puedes consultar el artículo original en inglés aquí: Zero Hydration Mismatch: Architecting dynamic announcement slots in Next.js App Router.