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
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
title | string | - | Título opcional sobre la galería |
class | string | - | Clase CSS adicional para el wrapper |
config | ParticleViewerConfig | {} | 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:
- 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.
- Fase de vuelo libre (0.3 - 0.75): Las partículas flotan con movimiento sinusoidal mientras viajan hacia su destino final.
- 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: reduceactivado - 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
Navegación táctil
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.