Componente de carousel 3D con perspectiva, totalmente escrito en TypeScript y agnostico de framework. Funciona con cualquier HTML estatico (Astro SSG, vanilla HTML, React, Vue, etc.).
El carousel toma tarjetas HTML existentes del DOM y les aplica transformaciones 3D (rotateY, scale, translateX) usando virtual scrolling. El JS es minimo: solo lee la posicion del scroll y calcula los transforms.
npm install
npm run devsrc/carousel-3d/
Carousel3D.ts # Clase principal (agnostica)
carousel-3d.css # Estilos del carousel
types.ts # Interfaces TypeScript
index.ts # Exports publicos
astro-demo/
Carousel3D.astro # Componente listo para Astro SSG
example-usage.astro # Ejemplo de uso en una pagina
El componente incluye un componente Astro listo para usar en astro-demo/Carousel3D.astro. Este componente implementa el patron de placeholder para evitar flash en la carga inicial.
---
import Carousel3D from '../components/Carousel3D.astro';
const destinos = [
{ name: 'Spain', flag: 'πͺπΈ', image: '/images/spain.jpg', description: '...' },
{ name: 'Maldives', flag: 'π²π»', image: '/images/maldives.jpg', description: '...' },
{ name: 'Greece', flag: 'π¬π·', image: '/images/greece.jpg', description: '...' },
// ... mas destinos
];
---
<Carousel3D
items={destinos}
visibleCards={3}
infinite={true}
showArrows={false}
showDots={true}
showDescription={false}
/>El carousel usa un sistema de dos capas para evitar el flash de contenido al cargar:
βββββββββββββββββββββββββββββββββββββββββββββββ
β .c3d-swap-container (position: relative) β
β β
β ββ #placeholder (visible, en flow) βββββββ β
β β Card fake izq | Card fake centro | der β β
β ββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββ #carousel-real (opacity:0, absolute) ββ β
β β [data-carousel-card] x N β β
β β (todas las cards reales) β β
β ββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββ
Flujo:
- Build time (SSG): Se renderizan las 3 fake cards con CSS puro que simula el carousel (mismos transforms 3D). El carousel real se renderiza oculto (
opacity: 0,position: absolute). - Primer paint: El usuario ve las fake cards perfectamente posicionadas. Sin JS, sin flash.
- JS carga: El
Carousel3Dconstruye el scroll virtual, posiciona las cards reales con transforms. - Swap (500ms): Tras medio segundo de espera (para que todo se asiente), el carousel real se muestra (
opacity: 1) y el placeholder se oculta (display: none).
Las 3 fake cards corresponden a lo que el carousel mostraria inicialmente:
- Izquierda: Ultima card de la lista (wrap infinito)
- Centro: Primera card de la lista
- Derecha: Segunda card de la lista
Los valores CSS de las fake cards son identicos a los transforms del JS:
| Posicion | translateX | scale | rotateY | z-index |
|---|---|---|---|---|
| Centro | 0px | 1 | 0deg | 30 |
| Izquierda | -220px | 0.8 | 21deg | 20 |
| Derecha | 220px | 0.8 | -21deg | 20 |
Estos valores se ajustan responsivamente en tablet y mobile.
| Prop | Tipo | Default | Descripcion |
|---|---|---|---|
items |
CarouselItem[] |
requerido | Array de items con name, image, y opcionalmente flag, description |
visibleCards |
3 | 5 |
3 |
Cantidad de tarjetas visibles |
infinite |
boolean |
true |
Scroll infinito |
showArrows |
boolean |
false |
Mostrar flechas prev/next |
showArrowsOnMobile |
boolean |
false |
Mostrar flechas en mobile |
showDots |
boolean |
true |
Mostrar indicadores de posicion |
showDescription |
boolean |
false |
Mostrar descripcion de la card activa |
id |
string |
'carousel' |
ID unico (necesario si hay multiples carousels) |
<link rel="stylesheet" href="carousel-3d.css" />
<!-- Placeholder (fake cards) -->
<div class="c3d-swap-container">
<div id="placeholder" class="c3d-placeholder">
<div class="c3d-placeholder-card c3d-placeholder-left"><!-- ultima card --></div>
<div class="c3d-placeholder-card c3d-placeholder-center"><!-- primera card --></div>
<div class="c3d-placeholder-card c3d-placeholder-right"><!-- segunda card --></div>
</div>
<!-- Carousel real (oculto) -->
<div id="mi-carousel" class="c3d-real-hidden">
<div data-carousel-card><!-- card 1 --></div>
<div data-carousel-card><!-- card 2 --></div>
<div data-carousel-card><!-- card 3 --></div>
</div>
</div>
<script type="module">
import { Carousel3D } from './carousel-3d/Carousel3D.js';
new Carousel3D(document.getElementById('mi-carousel'), {
visibleCards: 3,
infinite: true,
placeholderSelector: '#placeholder',
});
</script>| Opcion | Tipo | Default | Descripcion |
|---|---|---|---|
visibleCards |
3 | 5 |
3 |
Cantidad de tarjetas visibles. 3 muestra centro + 1 a cada lado. 5 muestra centro + 2 a cada lado. |
infinite |
boolean |
true |
Scroll infinito via virtual scrolling. Al llegar al final continua suavemente desde el inicio. Si es false, se detiene en los limites. |
showArrows |
boolean |
true |
Mostrar/ocultar flechas de navegacion prev/next. |
showArrowsOnMobile |
boolean |
true |
Mostrar/ocultar flechas en pantallas < 640px. Solo aplica si showArrows es true. |
placeholderSelector |
string |
undefined |
Selector CSS del contenedor placeholder (fake cards). Cuando el carousel esta listo, oculta el placeholder y se muestra. |
onActiveChange |
(index: number) => void |
undefined |
Callback cuando cambia la tarjeta activa. Recibe el indice (0-based). |
cardSelector |
string |
'[data-carousel-card]' |
Selector CSS para encontrar las tarjetas dentro del contenedor. |
Los dots y la descripcion se manejan fuera del componente core para total flexibilidad:
| Opcion | Descripcion |
|---|---|
| Dots | Usa .c3d-dots y .c3d-dot para estilos por defecto. Actualiza data-active="true" en onActiveChange. |
| Descripcion | Renderiza usando el indice de onActiveChange. |
| Metodo | Descripcion |
|---|---|
goTo(index) |
Navega suavemente a la tarjeta. En modo infinito toma la ruta mas corta. |
getActiveIndex() |
Retorna el indice de la tarjeta centrada. |
destroy() |
Limpia listeners y timers, restaura el DOM original. |
El carousel se adapta via ResizeObserver:
| Breakpoint | Ancho card | Alto card | Perspectiva |
|---|---|---|---|
| Desktop (1024px+) | 260px | 340px | 1050px |
| Tablet (640-1023px) | 220px | 290px | 800px |
| Mobile (< 640px) | 200px | 260px | 600px |
- Navegacion: Swipe/drag tactil (nativo), click en tarjeta lateral, flechas, dots
- Infinite scroll (default): Virtual scrolling con posicionamiento modular. Sin clonacion de DOM, sin reseteo de
scrollLeft - Modo finito (
infinite: false): Se detiene en la primera y ultima tarjeta - 3D:
perspective+rotateY+scaleen tarjetas laterales - SSG-friendly: Patron de placeholder para zero-flash en la carga inicial
| Clase | Descripcion |
|---|---|
.c3d-swap-container |
Contenedor que apila placeholder y carousel real |
.c3d-real-hidden |
Oculta el carousel real (opacity 0, position absolute) hasta que JS lo revele |
.c3d-placeholder |
Contenedor de fake cards con perspective |
.c3d-placeholder-card |
Card fake individual |
.c3d-placeholder-center |
Transform de la card central |
.c3d-placeholder-left |
Transform de la card izquierda (ultima del array) |
.c3d-placeholder-right |
Transform de la card derecha (segunda del array) |
.c3d-dots |
Contenedor de dots |
.c3d-dot |
Dot individual. Usa data-active="true" para el estado activo |