Más allá del parallax: cómo las animaciones CSS controladas por scroll están volviendo obsoletas las librerías JS
La API nativa de CSS Scroll-Driven Animations ha llegado silenciosamente a los navegadores, y es lo suficientemente buena como para reemplazar GSAP ScrollTrigger en la mayoría de los efectos que estás implementando. Esto es lo que realmente significa para tu stack.
Más allá del parallax: cómo las animaciones CSS controladas por scroll están volviendo obsoletas las librerías JS
Cada vez que haces npm install gsap para un scroll-fade o una barra de progreso fija, estás pagando un impuesto. Un impuesto de 67KB (minificado), un impuesto de análisis y ejecución, y cada vez más — uno innecesario. La API de CSS Scroll-Driven Animations lleva dos años construyéndose hacia la preparación para producción, y a mediados de 2024 ya cuenta con más del 75% de cobertura global en navegadores, con Chrome, Edge y Opera totalmente integrados. El soporte de Safari y Firefox está cerrando rápidamente la brecha.
Esto no es un artículo sobre «experimentos interesantes». Es una conversación franca entre desarrolladores sobre cuándo recurrir a la plataforma nativa y cuándo una librería todavía justifica su peso.
El impuesto de las animaciones scroll que hemos estado pagando
Seamos honestos sobre por qué las librerías de scroll en JavaScript se convirtieron en la opción predeterminada. Cuando apareció ScrollTrigger de GSAP, fue genuinamente revelador: suavidad, fiabilidad, anclaje que realmente funcionaba, scrubbing que se sentía perfectamente fluido. Framer Motion trajo el mismo poder a React con una API amigable con los componentes. Recurrimos a estas herramientas porque la alternativa era un caos frágil de callbacks de IntersectionObserver, bucles de requestAnimationFrame y listeners del evento scroll que arruinaban el rendimiento en dispositivos Android de gama media.
Pero esa alternativa ha cambiado.
«La mejor optimización de rendimiento es aquella que el navegador gestiona antes de que tu JavaScript siquiera se analice.»
Las animaciones controladas por scroll que se ejecutan de forma nativa en CSS funcionan fuera del hilo principal — el mismo lugar donde viven las transiciones CSS y las animaciones transform. Sin tiempo de análisis de JS. Sin sobrecarga de listeners. Sin tirones cuando tu hilo principal está ocupado hidratando un árbol de React.
La pregunta no es si lo nativo es mejor en teoría. Es si es suficientemente bueno en la práctica para cubrir tus casos de uso. Spoiler: para la mayoría del trabajo en agencias, sí lo es.
La API nativa explicada: animation-timeline sin tecnicismos
La especificación de Scroll-Driven Animations introduce dos tipos de línea de tiempo principales: scroll timelines y view timelines. Suenan similares, pero resuelven problemas distintos.
Scroll Timeline: vinculada a un contenedor de scroll
Una línea de tiempo scroll() progresa del 0% al 100% a medida que un contenedor de scroll se desplaza de arriba a abajo. El caso de uso clásico: esa barra de progreso de lectura en la parte superior de un artículo.
@keyframes grow-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
animation: grow-bar linear;
animation-timeline: scroll(root block);
transform-origin: left;
}
Eso es todo. Sin window.addEventListener('scroll'). Sin requestAnimationFrame. Sin los cálculos de element.scrollTop / document.body.scrollHeight. El navegador gestiona el cálculo por completo.
La función scroll() acepta dos argumentos: el scroller (root, nearest o un scroll-timeline-name con nombre) y el eje (block, inline, x, y).
View Timeline: vinculada a la visibilidad de un elemento
Aquí es donde las cosas se vuelven genuinamente emocionantes para los desarrolladores creativos. Una línea de tiempo view() progresa según la posición de un elemento dentro del viewport de su contenedor de scroll, no según la posición de scroll del documento. Piensa en ello como IntersectionObserver con esteroides y control por keyframes.
@keyframes fade-up {
entry 0% { opacity: 0; transform: translateY(40px); }
entry 100% { opacity: 1; transform: translateY(0); }
}
.card {
animation: fade-up linear both;
animation-timeline: view();
}
Las palabras clave de rango (entry, exit, contain, cover) te permiten especificar en qué fase del recorrido de scroll del elemento se impulsa la animación. entry se activa cuando el elemento entra en el viewport. exit cuando lo abandona. contain está activo mientras está completamente visible. Esta es la API que reemplaza directamente el caso de uso más común de ScrollTrigger: las animaciones de entrada desencadenadas por scroll.
Líneas de tiempo con nombre para coreografías complejas
Cuando necesitas que la posición de scroll de un elemento impulse la animación de otro (el parallax clásico), usas líneas de tiempo con nombre declaradas con scroll-timeline-name y consumidas por un elemento separado:
.scroll-container {
scroll-timeline-name: --my-timeline;
overflow-y: scroll;
}
.parallax-layer {
animation: drift linear;
animation-timeline: --my-timeline;
}
Análisis comparativo de rendimiento: nativo vs. librerías a escala
Las comparativas de rendimiento entre animaciones CSS nativas y librerías JS tienen matices, pero los datos se inclinan fuertemente hacia lo nativo para las cargas de trabajo adecuadas.
En pruebas replicadas a partir de los propios benchmarks del equipo de Chrome DevTools y el trabajo independiente de Adam Argyle (defensor de CSS en Google), los escenarios comunes pintan un panorama claro:
- Animación de barra de progreso: CSS nativo usa ~0ms de tiempo de ejecución JS. El equivalente con GSAP ScrollTrigger: 2–5ms por evento de scroll en un dispositivo de gama media.
- Animación escalonada de 20 elementos: CSS nativo con líneas de tiempo
view()— impacto en el hilo principal: insignificante. GSAP ScrollTrigger con el mismo efecto: 8–15ms por evento, causando frecuentemente caídas de fotogramas durante scrolls rápidos. - Capas de fondo parallax (3 capas): Aquí la brecha se estrecha. CSS nativo: ~1–2ms de trabajo de composición. GSAP con sugerencias
will-change: ~3–5ms. No es catastrófico, pero es medible.
La conclusión clave: las animaciones CSS controladas por scroll se ejecutan durante las fases de estilo y composición del navegador, no durante la ejecución de JS. En una página que ya está realizando trabajo significativo en JavaScript (enrutamiento SPA, carga diferida, peticiones de analíticas), esta separación marca la diferencia entre 60fps y 45fps en un Pixel 5.
Sitios como Linear.app y las páginas de marketing de Vercel han migrado públicamente hacia CSS nativo para sus efectos de scroll precisamente porque la ruta impulsada por el compositor elimina por completo el problema del «scroll con tirones bajo carga».
Donde JavaScript sigue ganando (siendo honestos)
Este artículo sería deshonesto si no reconociera dónde GSAP, Motion One y Framer Motion siguen teniendo un lugar legítimo en la mesa.
1. Secuenciación compleja de líneas de tiempo con callbacks
Si necesitas lanzar una petición fetch cuando una animación alcanza el 50%, deshabilitar un botón durante una transición vinculada al scroll, o encadenar efectos con then() — CSS no puede hacer esto. La API de línea de tiempo de GSAP con callbacks onUpdate y onComplete sigue siendo inigualable para animaciones con estado.
2. Animaciones basadas en física y de resorte
El easing linear() de CSS nos ha acercado, pero la física de resorte que responde dinámicamente a la velocidad (como useSpring de Framer Motion o el plugin inertia de GSAP) sigue siendo territorio JavaScript.
3. Integración con Canvas y WebGL Si tu scroll está impulsando una escena de Three.js, un canvas de Pixi.js o uniforms de shaders personalizados — estás en territorio JS, sin excepción. Librerías como Lenis para la normalización del scroll suave todavía tienen sentido aquí como capa de entrada del scroll, incluso si la salida es CSS nativo.
4. Paridad entre navegadores ahora mismo El soporte de Firefox llegó en la versión 110 (detrás de una bandera) y se está lanzando en estable a finales de 2024, pero si tu analítica muestra más del 15% de usuarios de Firefox y estás desarrollando sin mejora progresiva, todavía necesitas una estrategia de respaldo.
Paso a paso: reconstruyendo 3 efectos de scroll clásicos en CSS puro
Efecto 1: Sección fija con opacidad controlada por scrubbing
El efecto de «texto que se revela mientras haces scroll», antes un pilar de ScrollTrigger:
.sticky-section {
position: sticky;
top: 0;
height: 100vh;
}
@keyframes reveal-text {
from { opacity: 0; letter-spacing: 0.5em; }
to { opacity: 1; letter-spacing: normal; }
}
.sticky-section h2 {
animation: reveal-text linear both;
animation-timeline: view();
animation-range: entry 20% entry 80%;
}
Efecto 2: Galería de scroll horizontal
El scroll horizontal forzado que antes requería 40 líneas de JS:
.gallery-track {
display: flex;
width: 400vw;
scroll-timeline-name: --gallery;
}
.gallery-item {
animation: slide-in linear both;
animation-timeline: --gallery;
}
Combínalo con scroll-snap-type para esa sensación pulida e intencionada que antes requería fullPage.js.
Efecto 3: Imagen hero con parallax
@keyframes parallax-drift {
from { transform: translateY(-15%); }
to { transform: translateY(15%); }
}
.hero-image {
animation: parallax-drift linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
Treinta segundos de CSS. Sin librería. Sin evento de scroll. Funciona a 120fps en una pantalla ProMotion.
Mejora progresiva: no dejes atrás a Firefox
El patrón es limpio. Envuelve las declaraciones de la API nativa en una verificación @supports y deja que GSAP (cargado condicionalmente) se encargue de los navegadores no compatibles:
/* Base: sin animación */
.card { opacity: 0; transform: translateY(30px); transition: opacity 0.4s, transform 0.4s; }
/* Mejora progresiva */
@supports (animation-timeline: scroll()) {
.card {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 50%;
opacity: 1; /* Reinicio: la animación CSS gestiona esto */
transform: none;
}
}
En tu bundle de JS, usa la detección de características para importar condicionalmente tu librería de respaldo:
if (!CSS.supports('animation-timeline', 'scroll()')) {
import('./scroll-fallback.js').then(({ initFallbacks }) => initFallbacks());
}
Esto significa que la mayoría de tus usuarios no tienen ninguna sobrecarga de JS para los efectos de scroll, y la minoría obtiene un respaldo elegante.
Qué significa esto para el stack tecnológico de tu próximo proyecto
Aquí está el marco honesto para la decisión de tu próximo proyecto:
- ¿Sitio de marketing, portfolio o escaparate de agencia? Opta por defecto por las animaciones de scroll CSS nativas. Añade GSAP solo para efectos específicos que lo requieran.
- ¿Aplicación React/Next.js con animaciones interactivas complejas? Framer Motion sigue justificando su lugar. Pero aísla los efectos de entrada controlados por scroll en CSS — no importes una librería solo para hacer fade-ins.
- ¿Trabajo creativo intensivo en WebGL/Canvas? Mantén tu capa de animación JS. Considera Lenis para la entrada del scroll, y usa CSS donde los dos mundos no necesiten comunicarse.
La era en que npm install era la respuesta predeterminada a las animaciones de scroll está llegando a su fin — no porque las librerías de animación JavaScript no sean excelentes, sino porque la plataforma las ha alcanzado. Estudios galardonados como Active Theory y Resn ya están publicando trabajos que aprovechan la API nativa por su rendimiento y composabilidad.
Los desarrolladores que construirán las experiencias de scroll más rápidas e impresionantes en 2025 no serán los que mejor conozcan GSAP. Serán los que sepan exactamente cuándo no usarlo.
Empieza a auditar tus proyectos actuales. Elige un efecto de scroll que esté pasando por una librería JS y reconstruyelo en CSS puro esta semana. La pestaña de rendimiento no mentirá.
