Banner animado con CSS y HTML: código para copiar

Un banner animado con CSS pesa unos pocos kilobytes, se ve nítido en cualquier pantalla y no necesita ni una imagen. Frente a un GIF o un vídeo, la diferencia en tiempo de carga es enorme, y eso cuenta en tus Core Web Vitals.

Aquí tienes el código completo de cinco banners, listo para copiar. Ninguno depende de imágenes externas: pegas el HTML y el CSS y funcionan tal cual. Empezamos por el más sencillo y vamos subiendo.

El código HTML del banner

La estructura es deliberadamente mínima. Un contenedor, un titular, una línea de apoyo y el botón. Todo lo demás lo hace el CSS.

<a class="banner" href="/tu-pagina/">
  <h2>Rebajas de verano</h2>
  <p>Hasta un 40 % en toda la tienda</p>
  <span class="cta">Ver ofertas</span>
</a>

Fíjate en un detalle: el contenedor es un <a>, no un <div>. Un banner publicitario entero es un enlace, así que conviene que lo sea de verdad. Así funciona con teclado, el lector de pantalla lo anuncia bien y Google entiende a dónde lleva.

El CSS base y la entrada escalonada

Un banner de 300 x 250, que es la medida estándar del rectángulo mediano. Los tres elementos entran uno detrás de otro.

.banner {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: .6rem;
  width: 300px;
  height: 250px;
  padding: 1.6rem;
  box-sizing: border-box;
  overflow: hidden;
  border-radius: 12px;
  background: linear-gradient(135deg, #1e3a8a, #7c3aed);
  color: #fff;
  font-family: system-ui, sans-serif;
  text-decoration: none;
}

.banner h2 {
  margin: 0;
  font-size: 1.7rem;
  line-height: 1.1;
  animation: entra .6s .1s both;
}

.banner p {
  margin: 0;
  opacity: .9;
  animation: entra .6s .35s both;
}

.banner .cta {
  align-self: start;
  margin-top: .4rem;
  padding: .6rem 1.2rem;
  border-radius: 999px;
  background: #fff;
  color: #1e3a8a;
  font-weight: 600;
  animation: entra .6s .6s both;
}

@keyframes entra {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

La pieza clave es both al final de cada animación. Es el fill-mode: le dice al navegador que aplique el primer fotograma antes de empezar y mantenga el último al terminar. Sin él, los elementos aparecerían de golpe y luego saltarían al empezar su animación.

Anima siempre transform y opacity, nunca top, left o width. Las dos primeras las resuelve la tarjeta gráfica; las otras obligan al navegador a recalcular la maquetación en cada fotograma y el banner va a tirones en móvil.

See the Pen
Banner animado con CSS y HTML básico
by Isabel García (@Isabel-Garc-a)
on CodePen.

Efecto de brillo que barre

Ese destello diagonal que cruza el banner cada pocos segundos. Se hace con un pseudoelemento, así que no tocas el HTML.

.banner::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(255,255,255,.35) 50%,
    transparent 70%
  );
  transform: translateX(-100%);
  animation: brillo 3.5s 1.5s infinite;
  pointer-events: none;
}

@keyframes brillo {
  to { transform: translateX(100%); }
}

El pointer-events: none es obligatorio: sin él, el pseudoelemento tapa el banner entero y los clics no llegan al enlace. Es el fallo que más veces he tenido que depurar en banners ajenos.

See the Pen
Banner animado con CSS y HTML efecto brillo
by Isabel García (@Isabel-Garc-a)
on CodePen.

Banner rotativo de tres mensajes

El clásico de la publicidad: tres mensajes que se van turnando. Sin JavaScript, solo con retardos.

<div class="rotativo">
  <p>Envío gratis</p>
  <p>Devoluciones en 30 días</p>
  <p>Hasta un 40 % de descuento</p>
</div>
.rotativo {
  position: relative;
  width: 300px;
  height: 90px;
  border-radius: 10px;
  background: #111827;
  color: #fff;
  font-family: system-ui, sans-serif;
}

.rotativo p {
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-content: center;
  opacity: 0;
  animation: rota 12s infinite;
}

.rotativo p:nth-child(2) { animation-delay: 4s; }
.rotativo p:nth-child(3) { animation-delay: 8s; }

@keyframes rota {
  0%, 3%    { opacity: 0; transform: translateY(10px); }
  8%, 30%   { opacity: 1; transform: none; }
  35%, 100% { opacity: 0; transform: translateY(-10px); }
}

La cuenta es sencilla: doce segundos de ciclo entre tres mensajes salen a cuatro segundos cada uno, y los retardos van de cuatro en cuatro. Si añades un cuarto mensaje, sube el ciclo a dieciséis segundos, pon el retardo en 12s y ajusta los porcentajes dividiendo entre cuatro en vez de entre tres.

Botón con pulso

Una onda que sale del botón para llamar la atención sobre la llamada a la acción. Va con box-shadow a propósito: no ocupa espacio, así que no mueve nada de su sitio.

.banner .cta {
  animation: entra .6s .6s both, pulso 2s 1.4s infinite;
}

@keyframes pulso {
  0%   { box-shadow: 0 0 0 0 rgba(255,255,255,.6); }
  70%  { box-shadow: 0 0 0 14px rgba(255,255,255,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); }
}

Se pueden encadenar dos animaciones en la misma propiedad separándolas con coma. Aquí el botón primero entra y después empieza a latir.

Gradiente que gira

Este no se podía hacer en 2012 y hoy es de mis favoritos. Los degradados no son animables por defecto, pero con @property le dices al navegador que una variable es un ángulo y entonces sí sabe interpolarla.

@property --angulo {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.banner {
  background: linear-gradient(var(--angulo), #1e3a8a, #7c3aed);
  animation: gira 8s linear infinite;
}

@keyframes gira {
  to { --angulo: 360deg; }
}

Sustituye el background del CSS base por este. El fondo gira despacio y el banner deja de verse estático sin que nada se mueva de sitio.

Accesibilidad: el paso que casi nadie da

Hay gente a la que el movimiento en pantalla le provoca mareo o migraña, y por eso los sistemas operativos tienen una opción para reducir animaciones. Respetarla son tres líneas:

@media (prefers-reduced-motion: reduce) {
  .banner,
  .banner *,
  .banner::after,
  .rotativo p {
    animation: none !important;
  }
  .rotativo p { opacity: 1; }
  .rotativo p:nth-child(n+2) { display: none; }
}

Ojo al rotativo: si le quitas la animación sin más, los tres mensajes se superponen y queda ilegible. Por eso ahí se muestra solo el primero.

Esto ya no es solo buena práctica: en la Unión Europea hay obligaciones de accesibilidad en vigor y el movimiento incontrolable es uno de los puntos que se revisan. Si trabajas para clientes, incluirlo te ahorra la corrección después.

Qué ha cambiado desde 2012

Los tutoriales de banners en CSS que siguen circulando son casi todos de la época en que esto sustituyó a Flash. Estos cuatro puntos son los que de verdad han cambiado:

  • Se acabaron los prefijos. Nada de -webkit-animation ni -moz-keyframes. Escribe la propiedad limpia y ya está.
  • El fill-mode resolvió el problema del retardo. En 2012 no se podía usar animation-delay con garantías, así que se simulaba el retardo alargando la animación y metiendo fotogramas vacíos al principio. Con both eso ya no hace falta.
  • Los degradados se animan gracias a @property, que antes no existía.
  • Hay animaciones ligadas al scroll con animation-timeline, que dispara el banner cuando entra en pantalla en vez de al cargar la página. Aviso honesto: ronda el 84 % de soporte global, con Firefox todavía detrás de una opción de configuración. Si lo usas, envuélvelo en @supports (animation-timeline: scroll()) y deja el banner funcionando sin ello.

Si te interesa este terreno, en microinteracciones útiles con código tienes el mismo enfoque aplicado a botones y formularios, y en menús animados a la navegación.

Preguntas frecuentes

¿Dónde pego este código en WordPress?

El HTML va en un bloque HTML personalizado, dentro de la entrada o en un widget. El CSS va en Apariencia > Personalizar > CSS adicional, no dentro del bloque: así lo tienes en un solo sitio y no se duplica en cada página donde uses el banner.

¿Un banner en CSS afecta a los Core Web Vitals?

Para bien, si lo haces así. Al no cargar imágenes ni scripts no añade peticiones, y como solo animas transform y opacity no provoca desplazamientos de diseño. Un GIF equivalente puede pesar cien veces más.

¿Cómo lo hago adaptable al móvil?

Cambia el width: 300px por width: 100%; max-width: 300px; y quita la altura fija dejando que el contenido mande, o usa aspect-ratio: 6 / 5 para mantener la proporción del rectángulo mediano.

¿Se puede usar esto en Google Ads?

Para campañas de display, las plataformas piden el formato HTML5 con su propia estructura y sus límites de peso, así que no vale pegar esto tal cual. Donde sí funciona sin problema es en banners propios: tu web, tu newsletter, una landing o un aviso interno.

¿Por qué mi banner no responde a los clics?

Casi seguro es el pseudoelemento del brillo tapándolo. Añádele pointer-events: none y se arregla. Es el fallo más común de todos.


Con estos cinco bloques tienes un banner publicitario completo sin tocar una sola imagen ni una línea de JavaScript. Combínalos como quieras: el brillo funciona sobre cualquier fondo y el pulso sobre cualquier botón. En la categoría de CSS del blog tienes el resto de recursos, y si quieres seguir por la parte de maquetación, échale un vistazo a cómo hacer un slider. Que lo disfrutes.

Referencia histórica: la versión original de este artículo se apoyaba en este tutorial de Codrops de 2012, que sigue en línea y explica muy bien la lógica de animaciones anidadas, aunque su código necesita imágenes propias y usa técnicas que hoy ya no hacen falta.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio