
Animar elementos al hacer scroll sin JavaScript es una de esas técnicas que hace unos años parecía depender casi siempre de librerías externas, detectores de intersección, escuchas de eventos o soluciones personalizadas con scroll. Sin embargo, CSS ha avanzado mucho, y hoy podemos crear animaciones vinculadas al desplazamiento de una página de forma mucho más declarativa, limpia y cercana al propio lenguaje de estilos.
Cuando hablamos de animaciones scroll CSS sin JavaScript, no nos referimos únicamente a “hacer aparecer cosas con un poco de opacidad”. Hablamos de barras de progreso, textos que entran suavemente en pantalla, imágenes que se revelan al hacer scroll, secciones que se desplazan con efecto parallax moderado y componentes que reaccionan al avance del usuario dentro de una página.
La gran ventaja es clara: podemos mejorar la experiencia visual sin añadir complejidad innecesaria al proyecto. Eso sí, también conviene ser realistas. Las animaciones basadas en scroll con CSS moderno son muy potentes, pero todavía requieren una estrategia de compatibilidad progresiva. En otras palabras: podemos usarlas, pero debemos prepararlas para que la web siga funcionando correctamente aunque el navegador no soporte todas las propiedades más recientes.
En este artículo vamos a ver cómo animar al hacer scroll con CSS, qué propiedades modernas entran en juego, cuándo conviene usarlas, cómo plantear alternativas seguras y qué buenas prácticas deberías aplicar para que tus animaciones sean bonitas, accesibles y sostenibles.
Qué significa animar al hacer scroll sin JavaScript
Animar al hacer scroll significa que una animación no depende únicamente del paso del tiempo, como ocurre con una animación CSS tradicional. En una animación clásica, defines unos @keyframes, asignas una duración y el navegador reproduce la animación durante un número determinado de segundos.
Por ejemplo:
.card {
animation: fade-in 0.8s ease both;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Este código anima la tarjeta cuando se carga o cuando aparece en el flujo normal del documento, pero no está realmente vinculado al scroll. La animación ocurre por tiempo.
En cambio, una scroll animation CSS conecta el progreso de la animación con el desplazamiento del usuario. Es decir, la animación avanza, retrocede o se completa según la posición de scroll.
Esto permite crear efectos como:
- Barras de lectura que se llenan al avanzar por un artículo.
- Elementos que aparecen cuando entran en el viewport.
- Imágenes que se revelan progresivamente.
- Títulos que se desplazan o escalan de forma suave.
- Secciones que ganan opacidad a medida que el usuario baja.
La diferencia principal es conceptual: ya no pensamos en una duración fija, sino en una relación entre posición de scroll y progreso de animación.
La base técnica: CSS Scroll-Driven Animations
Las animaciones al hacer scroll sin JavaScript se apoyan en lo que se conoce como CSS Scroll-Driven Animations. Este conjunto de propiedades permite asociar una animación CSS a una línea temporal basada en el desplazamiento.
En lugar de utilizar únicamente la línea temporal tradicional del documento, CSS puede usar una línea temporal derivada del scroll. Esto abre la puerta a animaciones declarativas, sin necesidad de escuchar eventos scroll desde JavaScript.
Scroll timeline y view timeline
Dentro de este enfoque hay dos ideas importantes:
Scroll timeline
Una scroll timeline vincula la animación al progreso de desplazamiento de un contenedor. Es útil, por ejemplo, para crear una barra de progreso de lectura que se rellena desde el inicio hasta el final de la página.
En este caso, la animación responde al avance general del scroll.
View timeline
Una view timeline vincula la animación a la visibilidad de un elemento dentro del viewport. Es decir, el elemento puede empezar a animarse cuando entra en pantalla y completar la animación cuando alcanza una determinada zona visible.
Este enfoque es especialmente útil para animar elementos al hacer scroll CSS sin tener que detectar manualmente si el elemento está visible.
Ejemplo básico: animar una tarjeta al entrar en pantalla
Vamos a partir de un ejemplo sencillo. Imagina una serie de tarjetas dentro de una página de servicios, portfolio o blog. Queremos que cada tarjeta aparezca suavemente cuando el usuario llega a esa zona.
HTML de ejemplo
<section class="features">
<article class="feature-card">
<h2>Diseño responsive</h2>
<p>Interfaces adaptadas a cualquier tamaño de pantalla.</p>
</article>
<article class="feature-card">
<h2>Animaciones suaves</h2>
<p>Microinteracciones cuidadas para mejorar la experiencia.</p>
</article>
<article class="feature-card">
<h2>CSS moderno</h2>
<p>Soluciones visuales limpias sin depender siempre de JavaScript.</p>
</article>
</section>
CSS con animation-timeline: view()
.feature-card {
opacity: 0;
transform: translateY(40px);
animation: reveal-card linear both;
animation-timeline: view();
animation-range: entry 10% cover 35%;
}
@keyframes reveal-card {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Con este código, cada tarjeta se anima en relación con su entrada en pantalla. La propiedad animation-timeline: view() indica que la animación dependerá de la visibilidad del propio elemento.
La propiedad animation-range define en qué tramo de esa visibilidad empieza y termina la animación. En este caso, la tarjeta comienza a animarse cuando entra parcialmente en pantalla y completa su transición cuando ya ocupa una parte más clara del viewport.
Este patrón es ideal para crear una sensación de progresión sin convertir la página en un espectáculo excesivo. La animación acompaña al contenido, no compite con él.
Barra de progreso de lectura solo con CSS
Una de las aplicaciones más útiles de las animaciones vinculadas al scroll es la barra de progreso de lectura. Es habitual verla en artículos largos, guías técnicas o páginas de documentación.
La idea es sencilla: una barra fija en la parte superior de la página se va rellenando a medida que el usuario avanza.
HTML
<div class="reading-progress"></div>
CSS
.reading-progress {
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 4px;
transform-origin: left;
transform: scaleX(0);
animation: reading-progress linear both;
animation-timeline: scroll(root);
}
@keyframes reading-progress {
to {
transform: scaleX(1);
}
}
Aquí usamos animation-timeline: scroll(root), que vincula la animación al scroll del documento raíz. La barra empieza con scaleX(0) y termina con scaleX(1), creando una sensación de avance visual muy clara.
Este tipo de detalle mejora la orientación del usuario. En artículos extensos, una barra de progreso puede ayudar a entender cuánto contenido queda por leer sin añadir textos, porcentajes ni componentes adicionales.
Animaciones de revelado con imagen y texto
Otra técnica muy interesante consiste en revelar imágenes o bloques visuales a medida que entran en pantalla. Este efecto puede funcionar muy bien en portfolios, landing pages, páginas de producto o artículos visuales.
HTML
<figure class="image-reveal">
<img src="proyecto-web.jpg" alt="Vista previa de un proyecto web responsive">
</figure>
CSS
.image-reveal {
overflow: hidden;
}
.image-reveal img {
display: block;
width: 100%;
transform: scale(1.15);
opacity: 0;
animation: reveal-image linear both;
animation-timeline: view();
animation-range: entry 15% cover 45%;
}
@keyframes reveal-image {
to {
transform: scale(1);
opacity: 1;
}
}
En este caso combinamos opacity y transform, dos propiedades especialmente recomendables para animaciones porque suelen ser más eficientes que modificar dimensiones, márgenes o posiciones que obliguen al navegador a recalcular el layout.
El resultado es una imagen que aparece suavemente mientras reduce su escala. Es un efecto elegante, fácil de entender y suficientemente discreto para no saturar la experiencia.
Hacer que el texto acompañe al movimiento
También podemos aplicar una animación similar a los textos:
.scroll-title {
opacity: 0;
transform: translateY(32px);
animation: reveal-title linear both;
animation-timeline: view();
animation-range: entry 20% cover 40%;
}
@keyframes reveal-title {
to {
opacity: 1;
transform: translateY(0);
}
}
Este patrón funciona muy bien en encabezados de sección. La clave está en no abusar. Si cada párrafo, imagen, botón y tarjeta de una página se anima, la experiencia puede volverse cansada. La animación debe tener una intención: guiar, jerarquizar o reforzar una transición visual.
Compatibilidad: cómo usar CSS moderno sin romper la experiencia
Aquí conviene hacer una pausa importante. Aunque las animaciones de scroll con CSS son una herramienta muy prometedora, no deberías construir una experiencia que dependa al cien por cien de ellas.
La mejor estrategia es aplicar mejora progresiva. Es decir, primero defines un diseño funcional y accesible para todos los navegadores. Después, añades la animación solo cuando el navegador soporte las propiedades necesarias.
Usar @supports
Podemos envolver nuestras animaciones modernas dentro de una regla @supports:
.feature-card {
opacity: 1;
transform: none;
}
@supports (animation-timeline: view()) {
.feature-card {
opacity: 0;
transform: translateY(40px);
animation: reveal-card linear both;
animation-timeline: view();
animation-range: entry 10% cover 35%;
}
}
Este enfoque es mucho más seguro. Por defecto, la tarjeta se muestra normalmente. Si el navegador soporta animation-timeline: view(), entonces se aplica la animación.
Esto evita uno de los errores más peligrosos: dejar elementos con opacity: 0 en navegadores que no soportan la animación. Si eso ocurre, el contenido podría quedar invisible para parte de los usuarios.
Orden correcto de las propiedades
Un detalle importante: si usas la propiedad abreviada animation, declara animation-timeline después. Algunas propiedades abreviadas pueden reiniciar valores relacionados con la animación, por lo que conviene mantener un orden claro:
.element {
animation: reveal linear both;
animation-timeline: view();
}
Evita hacer esto:
.element {
animation-timeline: view();
animation: reveal linear both;
}
Aunque parezca un detalle menor, este tipo de orden puede marcar la diferencia entre una animación que funciona y otra que no se aplica como esperabas.
Buenas prácticas para animar al hacer scroll con CSS
Animar con scroll no consiste en añadir movimiento porque sí. Una buena animación tiene una función. Debe aportar claridad, ritmo o contexto.
Prioriza opacity y transform
Cuando sea posible, anima propiedades como:
opacitytransform: translatetransform: scaletransform: rotate
Estas propiedades suelen ser más adecuadas para animaciones fluidas porque no modifican directamente el flujo del documento.
En cambio, conviene evitar animar propiedades como:
widthheightmargintopleftpadding
No significa que estén prohibidas, pero pueden generar más trabajo de renderizado y afectar al rendimiento si se usan en exceso.
No conviertas cada scroll en una feria visual
Una página llena de animaciones puede parecer moderna durante los primeros segundos, pero cansar rápidamente. Lo recomendable es elegir puntos clave:
- La entrada de una sección importante.
- Una imagen destacada.
- Una barra de progreso.
- Un bloque de llamada a la acción.
- Una transición entre partes del contenido.
El movimiento debe acompañar al usuario, no interrumpirlo.
Cuida la duración percibida
En las animaciones scroll CSS sin JavaScript, la duración no se mide igual que en una animación temporal. Depende del tramo de scroll asignado. Por eso animation-range es tan importante.
Un rango demasiado corto puede hacer que el efecto sea brusco. Un rango demasiado largo puede hacer que la animación parezca lenta o imprecisa.
Una buena regla inicial es probar rangos como:
animation-range: entry 10% cover 35%;
O bien:
animation-range: entry 20% cover 50%;
Después ajusta según el tipo de elemento, el ritmo de la página y la importancia visual del contenido.
Accesibilidad: respeta prefers-reduced-motion
No todas las personas disfrutan de las animaciones. Para algunos usuarios, ciertos movimientos pueden resultar molestos, distraer o incluso provocar incomodidad. Por eso es fundamental respetar la preferencia del sistema mediante prefers-reduced-motion.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
}
}
También puedes ser más específica y desactivar solo las animaciones vinculadas al scroll:
@media (prefers-reduced-motion: reduce) {
.feature-card,
.image-reveal img,
.scroll-title {
animation: none;
opacity: 1;
transform: none;
}
}
Este segundo enfoque suele ser más controlado. Permite mantener la interfaz estable y evita que el contenido dependa del movimiento para ser comprensible.
La accesibilidad no debería verse como una capa final, sino como parte del diseño de la animación desde el principio.
CSS sin JavaScript no significa CSS sin estrategia
Usar CSS sin JavaScript para animaciones de scroll tiene muchas ventajas. Reduce dependencias, simplifica el mantenimiento y permite escribir efectos visuales de forma más declarativa. Pero eso no significa que debamos aplicarlo sin criterio.
Hay casos en los que CSS será suficiente:
- Efectos de aparición simples.
- Barras de progreso.
- Revelados visuales.
- Animaciones decorativas no críticas.
- Microinteracciones asociadas a la visibilidad del elemento.
Pero también hay casos en los que JavaScript puede seguir siendo necesario:
- Animaciones con lógica compleja.
- Estados dependientes de datos.
- Sincronización con componentes interactivos.
- Experiencias donde la animación modifica comportamiento real.
- Compatibilidad estricta con navegadores antiguos.
La clave está en elegir la herramienta adecuada. Si el efecto es visual, progresivo y no crítico, CSS puede ser una opción excelente. Si la animación forma parte de una lógica más compleja, quizá JavaScript siga teniendo sentido.
Ejemplo completo: sección animada al hacer scroll
Veamos un ejemplo más completo que combina buenas prácticas, mejora progresiva y accesibilidad.
HTML
<section class="services-section">
<header class="section-header">
<p class="eyebrow">Servicios web</p>
<h2>Interfaces modernas con CSS limpio y eficiente</h2>
<p>
Creamos experiencias visuales cuidadas, accesibles y adaptadas
a diferentes dispositivos.
</p>
</header>
<div class="services-grid">
<article class="service-card">
<h3>Diseño responsive</h3>
<p>Componentes pensados para funcionar en móvil, tablet y escritorio.</p>
</article>
<article class="service-card">
<h3>Animaciones CSS</h3>
<p>Microinteracciones suaves que mejoran la experiencia sin sobrecargarla.</p>
</article>
<article class="service-card">
<h3>Optimización visual</h3>
<p>Interfaces rápidas, claras y alineadas con los objetivos del proyecto.</p>
</article>
</div>
</section>
CSS
.services-section {
padding: 6rem 1.5rem;
}
.section-header,
.service-card {
opacity: 1;
transform: none;
}
.services-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.service-card {
padding: 1.5rem;
border-radius: 1rem;
background: #ffffff;
box-shadow: 0 16px 40px rgb(0 0 0 / 0.08);
}
@supports (animation-timeline: view()) {
.section-header {
opacity: 0;
transform: translateY(32px);
animation: reveal-block linear both;
animation-timeline: view();
animation-range: entry 10% cover 35%;
}
.service-card {
opacity: 0;
transform: translateY(40px);
animation: reveal-block linear both;
animation-timeline: view();
animation-range: entry 15% cover 40%;
}
}
@keyframes reveal-block {
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.section-header,
.service-card {
animation: none;
opacity: 1;
transform: none;
}
}
Este ejemplo tiene tres puntos importantes. Primero, el contenido es visible por defecto. Segundo, la animación solo se aplica si el navegador la soporta. Tercero, se respeta la preferencia de reducción de movimiento.
Es una base mucho más sólida que limitarse a copiar una animación vistosa sin pensar en compatibilidad, accesibilidad o mantenimiento.
Errores comunes al crear animaciones scroll CSS sin JavaScript
Aunque la técnica es relativamente sencilla, hay varios errores frecuentes que conviene evitar.
Dejar el contenido oculto por defecto
Si defines opacity: 0 fuera de @supports, puedes provocar que el contenido quede invisible en navegadores sin soporte. Es mejor que el estado base sea visible y que la animación se añada como mejora.
Animar demasiadas propiedades
Una animación que combina opacidad, escala, desplazamiento, blur, rotación y cambios de tamaño puede resultar pesada y confusa. Cuanto más simple sea el movimiento, más profesional suele sentirse.
No probar en dispositivos reales
Las animaciones pueden verse muy bien en escritorio y resultar incómodas en móvil. El ritmo de scroll, el tamaño del viewport y el rendimiento cambian mucho según el dispositivo.
Ignorar la accesibilidad
No respetar prefers-reduced-motion es un error importante. Las animaciones deben poder reducirse o eliminarse sin que la página pierda contenido ni sentido.
FAQs sobre animar elementos al hacer scroll sin JavaScript
¿Se puede animar al hacer scroll solo con CSS?
Sí, actualmente CSS permite crear animaciones vinculadas al scroll mediante propiedades como animation-timeline, scroll() y view(). Esto permite construir efectos visuales sin escuchar eventos de scroll con JavaScript. Aun así, conviene aplicar mejora progresiva porque no todos los navegadores ofrecen el mismo nivel de soporte.
¿Es mejor usar CSS o JavaScript para animaciones de scroll?
Depende del caso. Para efectos visuales sencillos, revelados de contenido, barras de progreso o animaciones decorativas, CSS puede ser una solución más limpia y mantenible. Para interacciones complejas, estados dinámicos o lógica avanzada, JavaScript puede seguir siendo necesario.
¿Las animaciones de scroll afectan al rendimiento?
Pueden afectar si se usan mal. Para mejorar el rendimiento, conviene animar propiedades como transform y opacity, evitar animaciones excesivas y probar en dispositivos reales. También es importante no depender de animaciones críticas para mostrar contenido esencial.
Reflexión final: animar menos, pero animar mejor
Animar elementos al hacer scroll sin JavaScript es una posibilidad muy atractiva para quienes trabajamos con CSS moderno. Nos permite crear experiencias más dinámicas, reducir dependencias y aprovechar mejor las capacidades nativas del navegador.
Sin embargo, la pregunta importante no es solo “¿puedo animarlo?”, sino “¿esta animación mejora realmente la experiencia?”. Una buena animación no debería estar para presumir de técnica, sino para acompañar la lectura, reforzar la jerarquía visual y hacer que la interfaz resulte más clara.
Las animaciones scroll CSS sin JavaScript tienen mucho potencial, especialmente cuando se combinan con accesibilidad, mejora progresiva y sentido del diseño. Usadas con moderación, pueden transformar una página estática en una experiencia más fluida y memorable. Usadas sin criterio, pueden convertirse en ruido.
Por eso, la mejor recomendación es empezar de forma sencilla: una barra de progreso, una tarjeta que aparece suavemente, una imagen que se revela al entrar en pantalla. A partir de ahí, prueba, mide, ajusta y elimina todo lo que no aporte valor.
En CSS, como en diseño, muchas veces la elegancia está en saber cuándo moverse y cuándo quedarse quieto.