Nuestra propia web tenía el problema de casa del zapatero: funcionaba, pero no contaba quiénes somos. La rehicimos entera. Este es el diario de cómo montamos el hero (la parte que más cuesta y más se recuerda) sin morir en el intento de rendimiento.
El punto de partida
Veníamos de un fondo con degradados verdes y unas partículas conectadas tipo constelación. Estaba bien, pero era genérico: lo has visto en mil plantillas. Queríamos algo que dijera ingeniería + creatividad a la primera: profundidad, movimiento lento y sofisticado, y un punto editorial casi de tráiler.
Fijamos tres reglas antes de tocar una línea de código: oscuro y limpio (midnight navy, no negro plano), cobalto y cyan como color de marca, y movimiento ambiental que realce el titular en lugar de competir con él.
Cinco capas, cada una con su herramienta
La clave de un fondo así es no meterlo todo en un mismo sitio. Lo separamos en capas y le dimos a cada una la tecnología que mejor rinde: degradado base y viñeta en CSS puro (coste cero), glows ambientales como radial-gradient, y, lo más importante, los anillos orbitales y las partículas en un único <canvas>.
Reservar el Canvas para lo único que de verdad se beneficia (decenas de puntos y arcos moviéndose) y dejar el resto en CSS estático. Un DOM lleno de divs animados con blur habría sido inmanejable.
Texto que se decodifica
El eslogan es «De cero a hero.». Le añadimos un efecto scramble: al cargar, «hero» y todo el subtítulo se decodifican desde caracteres aleatorios hasta el texto final, como una terminal. El detalle importante es que lo hicimos independiente del framerate: cada carácter tiene su ventana en milisegundos, así el decode dura siempre ~0,9 s aunque el equipo vaya lento.
// cada carácter resuelve por tiempo, no por frames
const start = Math.random() * 420; // ms
const end = start + 160 + Math.random() * 460; // termina ~580–1040 ms
queue.push({ from: old[i] || '', to: newText[i] || '', start, end });La guerra por los 60 fps
Aquí es donde el proyecto se puso interesante. La primera versión iba a 5 fps. Cinco. Medimos capa por capa y encontramos a los culpables: mix-blend-mode y filter: blur() en elementos grandes. Cada frame, el compositor tenía que rehacer el desenfoque de toda la pantalla porque algo (el canvas) cambiaba debajo.
Las soluciones: cambiar los glows de filter: blur a radial-gradient (un pintado, no un filtro que se re-rasteriza), quitar todos los mix-blend-mode, y mover los anillos de CSS (que rotaban una máscara cónica y producían un jitter sutil) a Canvas, donde se redibujan limpios en cada frame.
El 90 % del rendimiento de un fondo animado no está en tu algoritmo: está en no obligar al compositor a rehacer capas caras cada frame.
Nota mental, commit a las 2 a. m.
El resultado
Un hero que se ve desde el primer píxel (el fondo entra por debajo del header transparente, sin costuras), con una luz que gira despacio alrededor del titular, partículas que derivan, y el texto decodificándose. Y respeta prefers-reduced-motion: si has pedido menos movimiento, se congela en un frame estático perfectamente legible.
Rendimiento y belleza no están reñidos, pero se pelean. La versión bonita Y rápida salió de medir en vez de suponer: perfilar cada capa nos ahorró horas de retoques a ciegas.