Componente Astro Particle Image Viewer: Galería con Animaciones WebGL de Partículas

Componente Astro Particle Image Viewer: Galería con Animaciones WebGL de Partículas

He desarrollado @ivanalbizu/astro-particle-image-viewer, un componente Astro que transforma galerías de imágenes en experiencias visuales inmersivas. Al hacer clic en una miniatura, la imagen se descompone en miles de partículas que vuelan y se reensamblan en una vista a pantalla completa, todo renderizado con WebGL mediante Three.js.

Demo

Puedes ver el componente en acción en la demo interactiva.

Instalación

npm install @ivanalbizu/astro-particle-image-viewer

Three.js se incluye automáticamente como dependencia, no es necesario instalarlo por separado.

Uso básico

---
import { ParticleImageViewer } from '@ivanalbizu/astro-particle-image-viewer';
---

<ParticleImageViewer
title="Mi Galería"
config={{
  openDuration: 2000,
  closeDuration: 1200,
}}
>
<img src="/image-1.jpg" width="300" alt="Descripción imagen 1" />
<img src="/image-2.jpg" width="300" alt="Descripción imagen 2" />
<img src="/image-3.jpg" width="300" alt="Descripción imagen 3" />
</ParticleImageViewer>

El componente utiliza slots, permitiendo añadir cualquier elemento <img> como hijo. Cada imagen se envuelve automáticamente en un botón accesible.

Configuración

Props del componente

PropTipoDefaultDescripción
titlestring-Título opcional sobre la galería
classstring-Clase CSS adicional para el wrapper
configParticleViewerConfig{}Opciones de configuración de animación

ParticleViewerConfig

interface ParticleViewerConfig {
segments?: number;       // Resolución de la malla de partículas (default: 180, móvil: 80)
padding?: number;        // Multiplicador de padding (default: 1.1)
openDuration?: number;   // Duración animación apertura en ms (default: 2000)
closeDuration?: number;  // Duración animación cierre en ms (default: 1200)
crossfadeStart?: number; // Cuándo iniciar crossfade 0-1 (default: 0.2)
srcAttribute?: string;   // Atributo para source de imagen (default: 'src')
}

Aspectos técnicos destacados

Animación de partículas con shaders personalizados

El efecto visual se consigue mediante shaders GLSL personalizados que controlan el comportamiento de cada partícula individualmente. La animación se divide en tres fases:

  1. Fase de desintegración (0.0 - 0.4): Las partículas se dispersan desde su posición original siguiendo curvas de Bézier con offsets aleatorios.
  2. Fase de vuelo libre (0.3 - 0.75): Las partículas flotan con movimiento sinusoidal mientras viajan hacia su destino final.
  3. Fase de asentamiento (0.7 - 1.0): Las partículas se estabilizan suavemente en su posición final formando la imagen completa.

Durante el vuelo, las partículas adoptan formas aleatorias (cuadrados, círculos, triángulos) con rotación dinámica y efectos de motion blur basados en su velocidad.

Transiciones entre imágenes

Al navegar entre imágenes, se produce un efecto de “morphing” donde las partículas se dispersan con aberración cromática y glitch visual, intercambian la textura, y se reensamblan formando la nueva imagen.

Dynamic imports y code-splitting

Three.js se carga dinámicamente solo cuando es necesario. La librería se precarga en mouseenter o touchstart sobre las imágenes, garantizando que esté lista cuando el usuario haga clic:

// Three.js se carga solo al interactuar
item.addEventListener('mouseenter', () => this.loadThree());
item.addEventListener('touchstart', () => this.loadThree(), { passive: true });

Esto reduce significativamente el bundle inicial y mejora el tiempo de carga de la página.

Detección automática de rendimiento

El componente detecta automáticamente las capacidades del dispositivo y las preferencias del usuario:

import { shouldUseFallback, getFallbackReason } from '@ivanalbizu/astro-particle-image-viewer';

if (shouldUseFallback()) {
console.log('Usando SimpleLightbox:', getFallbackReason());
// 'prefers-reduced-motion' | 'low-performance-device' | 'webgl-not-supported'
}

Se activa el fallback SimpleLightbox (sin animaciones WebGL) cuando:

  • El usuario tiene prefers-reduced-motion: reduce activado
  • El dispositivo tiene poca memoria (< 4GB RAM)
  • El dispositivo tiene pocos cores de CPU (≤ 2)
  • El navegador tiene modo Data Saver activado
  • WebGL no está soportado

En dispositivos móviles, el número de segmentos se reduce automáticamente (80 en móvil vs 180 en desktop) para mantener un rendimiento fluido.

Soporte para Lazy Loading

El parámetro srcAttribute permite integrar el componente con librerías de lazy loading que usan atributos personalizados:

<ParticleImageViewer
config={{
  srcAttribute: 'data-src'
}}
>
<img data-src="/image-full.jpg" src="/placeholder.jpg" alt="Descripción" />
</ParticleImageViewer>

Personalización con CSS Custom Properties

El componente expone un amplio conjunto de variables CSS para facilitar la personalización:

.particle-image-viewer-wrapper {
/* Colores */
--piv-title-color: #333;
--piv-text-color: white;
--piv-button-bg: rgba(0, 0, 0, 0.6);
--piv-button-bg-hover: rgba(0, 0, 0, 0.8);
--piv-button-border: rgba(255, 255, 255, 0.5);
--piv-focus-ring: rgba(255, 255, 255, 0.9);

/* Espaciado */
--piv-spacing-sm: 8px;
--piv-spacing-md: 20px;
--piv-caption-bottom: 90px;
--piv-pagination-bottom: 30px;

/* Tamaños */
--piv-button-size: 50px;
--piv-dot-size: 44px;  /* WCAG touch target */
--piv-border-radius: 8px;

/* Transiciones */
--piv-transition-fast: 0.2s ease;
--piv-transition-normal: 0.3s ease;
--piv-transition-slow: 0.5s ease;

/* Blur (glassmorphism) */
--piv-blur-overlay: 5px;
--piv-blur-button: 4px;
--piv-blur-caption: 10px;
}

Accesibilidad

El componente implementa múltiples características de accesibilidad:

  • Navegación por teclado: Escape para cerrar, flechas para navegar, Tab para moverse entre controles
  • Focus trap: El foco queda atrapado dentro del lightbox mientras está abierto
  • Focus-visible: Estilos de foco visibles solo para usuarios de teclado
  • ARIA labels: Todos los botones tienen etiquetas descriptivas
  • Touch targets: Botones de paginación de 44px cumpliendo WCAG
  • Respeto a preferencias: Fallback automático para prefers-reduced-motion

En dispositivos móviles:

  • Swipe izquierda: Siguiente imagen
  • Swipe derecha: Imagen anterior
  • Tap fuera de imagen: Cerrar visor

Los gestos se detectan comparando el desplazamiento horizontal vs vertical, activándose solo cuando el movimiento horizontal supera un umbral de 50px.

Uso avanzado

Para mayor control, puedes usar las clases directamente:

import { ParticleViewer, SimpleLightbox } from '@ivanalbizu/astro-particle-image-viewer';

// Versión WebGL con animaciones de partículas
const viewer = new ParticleViewer(
'.my-container',
'.my-image-buttons',
{ openDuration: 3000, srcAttribute: 'data-src' }
);

// Versión simple sin animaciones
const lightbox = new SimpleLightbox(
'.my-container',
'.my-image-buttons',
'data-src'
);

// Limpieza al destruir
viewer.destroy();

Compatibilidad con View Transitions

El componente es totalmente compatible con Astro View Transitions. Gestiona automáticamente:

  • Limpieza de recursos antes de la navegación (astro:before-swap)
  • Reinicialización después de que la navegación se complete (astro:page-load)
  • Prevención de memory leaks mediante la correcta liberación de contextos WebGL y event listeners
  • Funcionamiento sin View Transitions - el componente funciona normalmente en páginas sin View Transitions activadas

No se requiere configuración adicional. El componente maneja ambos escenarios de forma transparente.

Compatibilidad

  • Astro: 4.x y 5.x
  • Navegadores: Todos los navegadores modernos con soporte WebGL
  • TypeScript: Tipos incluidos

Conclusión

@ivanalbizu/astro-particle-image-viewer demuestra que es posible crear experiencias visuales impactantes sin sacrificar rendimiento ni accesibilidad. La arquitectura de carga dinámica, la detección automática de capacidades, y el sistema de fallback garantizan que todos los usuarios tengan una experiencia óptima, independientemente de su dispositivo o preferencias.

El código fuente está disponible en GitHub y el paquete en npm.