Volver al blog
tecnologíaastrofrontendanimacionesarquitectura

Animaciones avanzadas con View Transitions en Astro 5

TroncoCorp 4 min de lectura

Con la llegada de Astro 5 y su soporte nativo para View Transitions, en TroncoCorp decidimos llevar la experiencia de navegación al siguiente nivel sin añadir dependencias pesadas ni JavaScript excesivo.

Este artículo explica cómo lo hicimos.

View Transitions: navegación fluida

Astro 5 incluye soporte integrado para la API de View Transitions del navegador. En lugar de recargar la página completa, el navegador anima la transición entre páginas usando CSS.

@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: view-fade-out 0.2s ease-out both;
}

::view-transition-new(root) {
  animation: view-fade-in 0.35s ease-out both;
}

Transiciones con nombre personalizado

Para elementos clave como el título de la página o el contenido principal, usamos transiciones con nombre:

<div data-view-transition="page-title">
  <h1>TroncoCorp</h1>
</div>

Esto permite que el título se deslice hacia arriba mientras el resto de la página se desvanece, creando una jerarquía visual en la transición.

Efecto de typing en el hero

El subtítulo del hero se escribe caracter por caracter usando JavaScript minimalista:

const text = el.textContent.trim();
let i = 0;
function typeChar() {
  if (i < text.length) {
    el.textContent += text.charAt(i);
    i++;
    setTimeout(typeChar, speed + Math.random() * 20);
  }
}

El Math.random() * 20 añade una ligera variación que hace que la animación se sienta más natural y menos robótica.

Partículas CSS (sin canvas)

En lugar de usar Canvas o WebGL para las partículas de fondo —que consumirían recursos significativos— optamos por una solución puramente CSS:

.particle-dot {
  position: absolute;
  width: 2px;
  height: 2px;
  background: var(--neon-green);
  border-radius: 50%;
  animation: particle-float linear infinite;
}
@keyframes particle-float {
  0% { transform: translateY(100vh) translateX(0); opacity: 0; }
  10% { opacity: 0.15; }
  90% { opacity: 0.15; }
  100% { transform: translateY(-10vh) translateX(30px); opacity: 0; }
}

30 puntos flotando verticalmente con colores verde neón, azul y naranja. Ligero, eficiente y visualmente impactante.

Parallax con requestAnimationFrame

El efecto parallax del hero usa requestAnimationFrame para garantizar 60fps incluso en dispositivos con limitaciones:

window.addEventListener('scroll', function() {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      parallaxLayers.forEach(layer => {
        layer.style.transform = 'translateY(' + (scrollY * 0.15) + 'px)';
      });
      ticking = false;
    });
    ticking = true;
  }
});

El patrón de throttling con requestAnimationFrame evita que el event listener de scroll sature el hilo principal.

Contadores animados

Los contadores de estadísticas —6+ proyectos activos, 72 años de proyección, 247 nodos— se animan cuando entran en el viewport usando IntersectionObserver:

const counterObserver = new IntersectionObserver(function(entries) {
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      animateCounter(entry.target);
      counterObserver.unobserve(entry.target);
    }
  });
}, { threshold: 0.5 });

El uso de threshold: 0.5 asegura que el contador solo se active cuando sea claramente visible.

Ripple en botones

Un micro-efecto ripple en los botones principales aporta feedback táctil:

btn.addEventListener('click', function(e) {
  const ripple = document.createElement('span');
  ripple.className = 'ripple';
  const size = Math.max(rect.width, rect.height);
  const x = e.clientX - rect.left - size / 2;
  const y = e.clientY - rect.top - size / 2;
  ripple.style.width = ripple.style.height = size + 'px';
  ripple.style.left = x + 'px';
  ripple.style.top = y + 'px';
  btn.appendChild(ripple);
  ripple.addEventListener('animationend', () => ripple.remove());
});

Respetando prefers-reduced-motion

Todas las animaciones se desactivan cuando el usuario tiene activada la preferencia de movimiento reducido:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Resultado

El sitio pesa menos de 50KB de JavaScript total, carga en menos de 2 segundos y ofrece una experiencia de navegación fluida que compite con aplicaciones SPA sin la complejidad ni el peso de un framework de frontend pesado.

Las animaciones no son decoración: son orientación visual que ayuda al usuario a entender dónde está y hacia dónde va.

Compartir

EN