Skip to content

oceangravity/css-3d-carousel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CSS 3D Carousel

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.

Instalacion

npm install
npm run dev

Estructura del proyecto

src/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

Astro SSG (recomendado)

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.

Uso rapido

---
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}
/>

Como funciona el patron de placeholder (fake cards)

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:

  1. 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).
  2. Primer paint: El usuario ve las fake cards perfectamente posicionadas. Sin JS, sin flash.
  3. JS carga: El Carousel3D construye el scroll virtual, posiciona las cards reales con transforms.
  4. 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).

Fake cards: que se muestra

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.

Props del componente Astro

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)

HTML vanilla

<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>

Configuracion

Opciones del constructor (Carousel3DOptions)

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.

Opciones del consumidor (dots y descripcion)

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.

Metodos publicos

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.

Responsive

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

Comportamiento

  • 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 + scale en tarjetas laterales
  • SSG-friendly: Patron de placeholder para zero-flash en la carga inicial

Clases CSS del componente

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